The title of this post is slightly misleading in that what I’m really aiming to do is to “appear” to dynamically add properties to an object at runtime, which can then be discover-able by calls to the TypeDescriptor GetProperties method.
What’s the use case ?
The most obvious use for this “technique” is in UI programming whereby you might have an object that either you want to extend to “appear” to have more properties than it really has, or a more likely scenario is where an object has an array of values which you want it to appear as if the items were properties on the object. For example
public class MyObject { public string Name { get; set; } public int[] Values { get; set; } }
We might want this appear as if the Values are actually properties, named P1 to Pn. The is particularly useful when data binding to a Grid control of some sort, so wanting to see the Name and Values as columns in the grid.
Let’s look at how we achieve this…
Eh ? Can I see some code ?
The above use case is an actual one we have on the project I’m working on, but let’s do something a lot simpler to demonstrate the concepts. Let’s instead create an object with three properties and simply dynamically add a fourth property, the class looks like this
public class ThreeColumns { public string One { get; set; } public string Two { get; set; } public string Three { get; set; } }
I’ll now demonstrate the code that (when implemented) we’ll use to create an add a fourth property before looking at the actual code that achieves this
// this line would tend to be a class instance variable to ensure the dynamic propetries // are not GC'd unexpectedly DynamicPropertyManager<ThreeColumns> propertyManager; propertyManager = new DynamicPropertyManager<ThreeColumns>(); propertyManager.Properties.Add( DynamicPropertyManager<ThreeColumns>.CreateProperty<ThreeColumns, string>( "Four", t => GetTheValueForFourFromSomewhere(), null ));
So the idea is that we’ll aim to create a property manager class which then allows us to add properties to the type, ThreeColumns – but remember these added properties will only be visible by code using the TypeDescriptor to get the list of properties from the object.
If we now created a list of ThreeColumn objects and databind to the DataSource of a grid (such as the Infragistics UltraGrid or XamDataGrid) it would show the four columns for the three real properties and the dynamically created one.
Implementation
Alright we’ve seen the “end game”, let’s now look at how we’re going to create the property manager which will be used to maintain and give access to an implementation of a TypeDescriptionProvider. The property manager looks like this
public class DynamicPropertyManager<TTarget> : IDisposable { private readonly DynamicTypeDescriptionProvider provider; private readonly TTarget target; public DynamicPropertyManager() { Type type = typeof (TTarget); provider = new DynamicTypeDescriptionProvider(type); TypeDescriptor.AddProvider(provider, type); } public DynamicPropertyManager(TTarget target) { this.target = target; provider = new DynamicTypeDescriptionProvider(typeof(TTarget)); TypeDescriptor.AddProvider(provider, target); } public IList<PropertyDescriptor> Properties { get { return provider.Properties; } } public void Dispose() { if (ReferenceEquals(target, null)) { TypeDescriptor.RemoveProvider(provider, typeof(TTarget)); } else { TypeDescriptor.RemoveProvider(provider, target); } } public static DynamicPropertyDescriptor<TTargetType, TPropertyType> CreateProperty<TTargetType, TPropertyType>( string displayName, Func<TTargetType, TPropertyType> getter, Action<TTargetType, TPropertyType> setter, Attribute[] attributes) { return new DynamicPropertyDescriptor<TTargetType, TPropertyType>( displayName, getter, setter, attributes); } public static DynamicPropertyDescriptor<TTargetType, TPropertyType> CreateProperty<TTargetType, TPropertyType>( string displayName, Func<TTargetType, TPropertyType> getHandler, Attribute[] attributes) { return new DynamicPropertyDescriptor<TTargetType, TPropertyType>( displayName, getHandler, (t, p) => { }, attributes); } }
In the above code you’ll notice we can create our dynamic properties on both a type and an instance of a type. Beware, not all UI controls will query for the properties on an instance, but instead will just get those on the type.
So as mentioned the property manager basically manages the lifetime of our TypeDescriptionProvider implementation. So let’s take a look at that code now
public class DynamicTypeDescriptionProvider : TypeDescriptionProvider { private readonly TypeDescriptionProvider provider; private readonly List<PropertyDescriptor> properties = new List<PropertyDescriptor>(); public DynamicTypeDescriptionProvider(Type type) { provider = TypeDescriptor.GetProvider(type); } public IList<PropertyDescriptor> Properties { get { return properties; } } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { return new DynamicCustomTypeDescriptor( this, provider.GetTypeDescriptor(objectType, instance)); } private class DynamicCustomTypeDescriptor : CustomTypeDescriptor { private readonly DynamicTypeDescriptionProvider provider; public DynamicCustomTypeDescriptor(DynamicTypeDescriptionProvider provider, ICustomTypeDescriptor descriptor) : base(descriptor) { this.provider = provider; } public override PropertyDescriptorCollection GetProperties() { return GetProperties(null); } public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { var properties = new PropertyDescriptorCollection(null); foreach (PropertyDescriptor property in base.GetProperties(attributes)) { properties.Add(property); } foreach (PropertyDescriptor property in provider.Properties) { properties.Add(property); } return properties; } } }
Note: In the inner class DynamicCustomTypeDescriptor we simply append our dynamic properties to the existing ones when creating the PropertyDescriptorCollection however we could replace/merge properties with the existing object’s. So for example replace/intercept an existing property. Also I’ve made the code as simple as possible, but it’s most likely you’d want to look to cache the properties when the PropertyDescriptorCollection is created to save having to get them every time.
So the purpose of the DynamicTypeDescriptionProvider is to basically build our property list and then intercept and handle calls to the GetProperties methods.
Finally, we want a way to create our new properties (via the CreateProperty methods on the DynamicPropertyManager, so now we need to implement our property descriptors
public class DynamicPropertyDescriptor<TTarget, TProperty> : PropertyDescriptor { private readonly Func<TTarget, TProperty> getter; private readonly Action<TTarget, TProperty> setter; private readonly string propertyName; public DynamicPropertyDescriptor( string propertyName, Func<TTarget, TProperty> getter, Action<TTarget, TProperty> setter, Attribute[] attributes) : base(propertyName, attributes ?? new Attribute[] { }) { this.setter = setter; this.getter = getter; this.propertyName = propertyName; } public override bool Equals(object obj) { var o = obj as DynamicPropertyDescriptor<TTarget, TProperty>; return o != null && o.propertyName.Equals(propertyName); } public override int GetHashCode() { return propertyName.GetHashCode(); } public override bool CanResetValue(object component) { return true; } public override Type ComponentType { get { return typeof (TTarget); } } public override object GetValue(object component) { return getter((TTarget)component); } public override bool IsReadOnly { get { return setter == null; } } public override Type PropertyType { get { return typeof(TProperty); } } public override void ResetValue(object component) { } public override void SetValue(object component, object value) { setter((TTarget) component, (TProperty) value); } public override bool ShouldSerializeValue(object component) { return true; } }
Much of this code is just creates default methods for the abstract class PropertyDescriptor, but as you can see the GetValue and SetValue call our interceptors which we registered with the property manager.
That’s basically that. So now anything calling TypeDescriptor.GetProperties will see our new properties (and their attributes) and interact with those properties through our inteceptor methods.
If you recall the code for creating the property manager we can use the following to confirm that, indeed TypeDescriptor.GetProperties see’s our ThreeColumns object as having four properties
static void Main(string[] args) { DynamicPropertyManager<ThreeColumns> propertyManager; propertyManager = new DynamicPropertyManager<ThreeColumns>(); propertyManager.Properties.Add( DynamicPropertyManager<ThreeColumns>.CreateProperty<ThreeColumns, string>( "Four", t => "Four", null )); var p = TypeDescriptor.GetProperties(typeof (ThreeColumns)); Console.WriteLine(p.Count); // outputs 4 instead of the 3 real properties }