175 lines
12 KiB
Markdown
175 lines
12 KiB
Markdown
|
|
---
|
|||
|
|
source_files:
|
|||
|
|
- Common/DTS.CommonCore/Base/Classes/BasePropertyChanged.cs
|
|||
|
|
- Common/DTS.CommonCore/Base/Classes/DisplayResourceAttribute.cs
|
|||
|
|
- Common/DTS.CommonCore/Base/Classes/DescriptionResourceAttribute.cs
|
|||
|
|
- Common/DTS.CommonCore/Base/Classes/DynamicTypeDescriptor.cs
|
|||
|
|
generated_at: "2026-04-16T02:51:28.846308+00:00"
|
|||
|
|
model: "Qwen/Qwen3-Coder-Next-FP8"
|
|||
|
|
schema_version: 1
|
|||
|
|
sha256: "e84e534d3f8838df"
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Classes
|
|||
|
|
|
|||
|
|
## Documentation: `DTS.Common.Base` Module – Property Change Notification & Dynamic Type Descriptor Infrastructure
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 1. **Purpose**
|
|||
|
|
|
|||
|
|
This module provides foundational infrastructure for property change notification and dynamic runtime customization of object metadata—primarily to support UI frameworks like `PropertyGrid` where static attributes (e.g., `DisplayName`, `Description`) must be overridden or extended at runtime. It includes a base class (`BasePropertyChanged`) for implementing `INotifyPropertyChanged`, and two custom attribute classes (`DisplayResourceAttribute`, `DescriptionResourceAttribute`) that delegate display strings to localized resources via `Strings.Strings.ResourceManager`. Additionally, `DynamicTypeDescriptor` enables dynamic modification of property metadata (e.g., browsability, display name, category) for objects, especially useful for types like Entity Framework proxies where attributes cannot be applied directly.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. **Public Interface**
|
|||
|
|
|
|||
|
|
#### `BasePropertyChanged`
|
|||
|
|
- **`event PropertyChangedEventHandler PropertyChanged`**
|
|||
|
|
Virtual event for property change notifications. Subclasses may override to customize notification behavior.
|
|||
|
|
|
|||
|
|
- **`bool SetProperty<T>(ref T storage, T value, string propertyName = null)`**
|
|||
|
|
Compares `storage` and `value` for equality; if different, updates `storage`, invokes `OnPropertyChanged`, and returns `true`. Returns `false` if values are equal. Used to implement `INotifyPropertyChanged` concisely.
|
|||
|
|
|
|||
|
|
- **`virtual void OnPropertyChanged(string propertyName = null)`**
|
|||
|
|
Raises the `PropertyChanged` event with the given property name. If `propertyName` is `null`, raises `PropertyChangedEventArgs.Empty`.
|
|||
|
|
|
|||
|
|
#### `DisplayResourceAttribute`
|
|||
|
|
- **`DisplayResourceAttribute(string resourceId)`**
|
|||
|
|
Constructor. Stores `resourceId` for later lookup in `Strings.Strings.ResourceManager`.
|
|||
|
|
|
|||
|
|
- **`override string DisplayName`**
|
|||
|
|
Returns the localized string from `Strings.Strings.ResourceManager.GetString(resourceId)`, or `"##ResourceNotFound##" + resourceId` if not found.
|
|||
|
|
|
|||
|
|
#### `DescriptionResourceAttribute`
|
|||
|
|
- **`DescriptionResourceAttribute(string resourceId)`**
|
|||
|
|
Constructor. Stores `resourceId` for later lookup in `Strings.Strings.ResourceManager`.
|
|||
|
|
|
|||
|
|
- **`override string Description`**
|
|||
|
|
Returns the localized string from `Strings.Strings.ResourceManager.GetString(resourceId)`, or `"##DescriptionNotFound##" + resourceId` if not found.
|
|||
|
|
|
|||
|
|
#### `DynamicTypeDescriptor`
|
|||
|
|
- **`DynamicTypeDescriptor(Type type)`**
|
|||
|
|
Constructor. Initializes internal state by querying `TypeDescriptor` for the given `type`. Filters out non-browsable properties. Stores original properties, type converter, default event/property, and editors.
|
|||
|
|
|
|||
|
|
- **`T GetPropertyValue<T>(string name, T defaultValue)`**
|
|||
|
|
Retrieves the value of the property named `name` from the current `Component`, casting to `T`. Returns `defaultValue` on failure or if property not found.
|
|||
|
|
|
|||
|
|
- **`void SetPropertyValue(string name, object value)`**
|
|||
|
|
Sets the value of the property named `name` on the current `Component`. No-op if property not found.
|
|||
|
|
|
|||
|
|
- **`DynamicProperty AddProperty(Type type, string name, object value, string displayName, string description, string category, bool hasDefaultValue, object defaultValue, bool readOnly, Type uiTypeEditor)`**
|
|||
|
|
Creates and adds a new `DynamicProperty` with specified metadata. Overload omits `uiTypeEditor` (defaults to `null`).
|
|||
|
|
|
|||
|
|
- **`void AddProperty(PropertyDescriptor property)`**
|
|||
|
|
Adds a pre-constructed `PropertyDescriptor` (e.g., `DynamicProperty`) to the `Properties` collection.
|
|||
|
|
|
|||
|
|
- **`void RemoveProperty(string name)`**
|
|||
|
|
Removes the property named `name` from the `Properties` collection.
|
|||
|
|
|
|||
|
|
- **`DynamicTypeDescriptor FromComponent(object component)`**
|
|||
|
|
Creates a new `DynamicTypeDescriptor` instance bound to `component`, copying original metadata and creating `DynamicProperty` wrappers for each property. Requires `component` to be assignable to the original `_type`.
|
|||
|
|
|
|||
|
|
- **`event PropertyChangedEventHandler PropertyChanged`**
|
|||
|
|
Implements `INotifyPropertyChanged`. Raised internally via `OnValueChanged(PropertyDescriptor)` when a property value changes.
|
|||
|
|
|
|||
|
|
- **`PropertyDescriptorCollection Properties { get; }`**
|
|||
|
|
Mutable collection of *browsable* properties (original + dynamically added/modified). Non-browsable properties are excluded during construction.
|
|||
|
|
|
|||
|
|
- **`PropertyDescriptorCollection OriginalProperties { get; }`**
|
|||
|
|
Immutable snapshot of the original properties (including non-browsable ones) as returned by `TypeDescriptor.GetProperties(type)`.
|
|||
|
|
|
|||
|
|
- **`object Component { get; }`**
|
|||
|
|
The object instance whose properties are being described. Set only during `FromComponent`.
|
|||
|
|
|
|||
|
|
- **`string ClassName { get; set; }` / `string ComponentName { get; set; }`**
|
|||
|
|
Overridable display names for the type and component, respectively, used in `ICustomTypeDescriptor.GetClassName()`/`GetComponentName()`.
|
|||
|
|
|
|||
|
|
#### `DynamicTypeDescriptor.DynamicProperty`
|
|||
|
|
- **`DynamicProperty(DynamicTypeDescriptor descriptor, Type type, object value, string name, Attribute[] attrs)`**
|
|||
|
|
Internal constructor for a new dynamic property (not tied to an existing property descriptor).
|
|||
|
|
|
|||
|
|
- **`DynamicProperty(DynamicTypeDescriptor descriptor, PropertyDescriptor existing, object component)`**
|
|||
|
|
Internal constructor wrapping an *existing* property descriptor, capturing its value and attributes.
|
|||
|
|
|
|||
|
|
- **`void SetDisplayName(string displayName)` / `void SetDescription(string description)` / `void SetCategory(string category)`**
|
|||
|
|
Overrides the corresponding property metadata (e.g., `DisplayName`, `Description`, `Category`) for this dynamic property.
|
|||
|
|
|
|||
|
|
- **`void SetBrowsable(bool browsable)` / `void SetIsReadOnly(bool readOnly)`**
|
|||
|
|
Overrides `IsBrowsable` and `IsReadOnly` behavior.
|
|||
|
|
|
|||
|
|
- **`void RemoveAttributesOfType<T>()`**
|
|||
|
|
Removes all attributes of type `T` (or derived) from the internal `_attributes` list.
|
|||
|
|
|
|||
|
|
- **`IList<Attribute> AttributesList { get; }`**
|
|||
|
|
Exposes the internal list of attributes for inspection/modification.
|
|||
|
|
|
|||
|
|
- **`override object GetValue(object component)` / `override void SetValue(object component, object value)`**
|
|||
|
|
Gets/sets the property value. If wrapping an `_existing` property, delegates to it; otherwise uses the internal `Value` field.
|
|||
|
|
|
|||
|
|
- **`override void ResetValue(object component)`**
|
|||
|
|
Resets value to default (if `_existing` exists, delegates; otherwise uses `_defaultValue` or `Value`).
|
|||
|
|
|
|||
|
|
- **`override bool IsBrowsable` / `override bool IsReadOnly` / `override string DisplayName` / `override string Description` / `override string Category`**
|
|||
|
|
Returns overridden values if set; otherwise falls back to base implementation or wrapped `_existing` property.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. **Invariants**
|
|||
|
|
|
|||
|
|
- **`BasePropertyChanged.SetProperty`**
|
|||
|
|
- Only raises `PropertyChanged` if `storage` and `value` are *not* equal (via `Equals`).
|
|||
|
|
- `storage` is updated *before* `OnPropertyChanged` is called.
|
|||
|
|
|
|||
|
|
- **`DynamicTypeDescriptor`**
|
|||
|
|
- `Properties` collection excludes non-browsable properties *at construction time* (via `if (!property.IsBrowsable) continue;`).
|
|||
|
|
- `FromComponent` performs a *shallow copy* of internal state (e.g., `_editors`, `_typeConverter`), but creates *new* `DynamicProperty` instances per property.
|
|||
|
|
- `DynamicProperty` overrides metadata *only if explicitly set* via `Set*` methods; otherwise falls back to original or base behavior.
|
|||
|
|
- `DynamicTypeDescriptor` implements `ICustomTypeDescriptor` and `INotifyPropertyChanged`—consumers may rely on both interfaces.
|
|||
|
|
|
|||
|
|
- **`DisplayResourceAttribute` / `DescriptionResourceAttribute`**
|
|||
|
|
- `DisplayName`/`Description` always returns a non-null string: either the localized value or a fallback marker (`##ResourceNotFound##`/`##DescriptionNotFound##`) concatenated with the `resourceId`.
|
|||
|
|
- Resource lookup uses `Strings.Strings.ResourceManager.GetString(resourceId)`—*no* automatic suffixing (e.g., `"PropertyName_Description"` must be the exact `resourceId` passed to the constructor).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4. **Dependencies**
|
|||
|
|
|
|||
|
|
- **Internal Dependencies**
|
|||
|
|
- `System.ComponentModel` (`INotifyPropertyChanged`, `PropertyChangedEventHandler`, `Attribute`, `PropertyDescriptor`, etc.).
|
|||
|
|
- `Strings.Strings.ResourceManager` (from `DTS.Common` resources) for `DisplayResourceAttribute` and `DescriptionResourceAttribute`.
|
|||
|
|
- `System.Drawing.Design` (`UITypeEditor`, `EditorAttribute`) for editor support in `DynamicTypeDescriptor`.
|
|||
|
|
|
|||
|
|
- **External Dependencies**
|
|||
|
|
- `DynamicTypeDescriptor` is used by UI components (e.g., `PropertyGrid`) that rely on `ICustomTypeDescriptor`.
|
|||
|
|
- `BasePropertyChanged` is intended for use by view models or entities requiring `INotifyPropertyChanged`.
|
|||
|
|
- `DisplayResourceAttribute`/`DescriptionResourceAttribute` are used as attributes on properties to enable localization.
|
|||
|
|
|
|||
|
|
- **Inferred Usage**
|
|||
|
|
- Likely consumed by UI layers (e.g., WinForms `PropertyGrid`, WPF data binding) where dynamic metadata or localization is needed.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. **Gotchas**
|
|||
|
|
|
|||
|
|
- **`DynamicTypeDescriptor` does *not* automatically propagate changes to the underlying `Component`** when using `DynamicProperty` with no `_existing` property. `SetValue`/`ResetValue` only update the internal `Value` field of the `DynamicProperty`. To bind to real properties, use `FromComponent` to wrap an existing property descriptor.
|
|||
|
|
|
|||
|
|
- **`DynamicProperty.Attributes` is a *snapshot* of `_attributes` at construction time** unless modified via `RemoveAttributesOfType<T>` or `Set*` methods. Direct mutation of `_attributes` is not exposed.
|
|||
|
|
|
|||
|
|
- **`DisplayResourceAttribute` and `DescriptionResourceAttribute` do *not* support fallback to property name**—if the resource ID is missing, the marker string (`##ResourceNotFound##`/`##DescriptionNotFound##`) is returned *as-is*, including the ID. This may cause UI clutter if not handled.
|
|||
|
|
|
|||
|
|
- **`BasePropertyChanged.SetProperty` uses `Equals` for comparison**, which may be unsafe for mutable reference types (e.g., collections). Consider using `EqualityComparer<T>.Default` or custom comparison if needed.
|
|||
|
|
|
|||
|
|
- **`DynamicTypeDescriptor` filters properties by `IsBrowsable` at construction time**, so non-browsable properties (e.g., `DesignerSerializationVisibility.Hidden`) are permanently excluded from `Properties`. Use `OriginalProperties` to inspect all properties.
|
|||
|
|
|
|||
|
|
- **`DynamicTypeDescriptor.FromComponent` requires `component.GetType()` to be assignable to `_type`**. Passing a derived type may throw `ArgumentException`.
|
|||
|
|
|
|||
|
|
- **`DynamicProperty`’s `ShouldSerializeValue` always returns `false` unless wrapping an `_existing` property**—this may interfere with serialization logic expecting `ShouldSerialize*` semantics.
|
|||
|
|
|
|||
|
|
- **No thread-safety guarantees** are documented or implied for `DynamicTypeDescriptor` or `BasePropertyChanged`. Concurrent access to `Properties` or `PropertyChanged` events may require external synchronization.
|
|||
|
|
|
|||
|
|
- **`Strings.Strings.ResourceManager` must be initialized before `DisplayResourceAttribute`/`DescriptionResourceAttribute` are instantiated**, or `GetString` will return `null`. This is a runtime dependency on the application’s localization setup.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*Documentation generated from source files only. No external behavior or assumptions beyond the provided code.*
|