--- 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(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(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()`** Removes all attributes of type `T` (or derived) from the internal `_attributes` list. - **`IList 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` 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.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.*