How do I create dynamic properties in C#?
Asked Answered
S

12

93

I am looking for a way to create a class with a set of static properties. At run time, I want to be able to add other dynamic properties to this object from the database. I'd also like to add sorting and filtering capabilities to these objects.

How do I do this in C#?

Salutation answered 3/6, 2009 at 21:3 Comment(1)
What is the purpose of this class? Your request makes me suspicious that you really need a design pattern or something, though not knowing what your use case is means I don't actually have a suggestion.Scrivens
P
65

You might use a dictionary, say

Dictionary<string,object> properties;

I think in most cases where something similar is done, it's done like this.
In any case, you would not gain anything from creating a "real" property with set and get accessors, since it would be created only at run-time and you would not be using it in your code...

Here is an example, showing a possible implementation of filtering and sorting (no error checking):

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1 {

    class ObjectWithProperties {
        Dictionary<string, object> properties = new Dictionary<string,object>();

        public object this[string name] {
            get { 
                if (properties.ContainsKey(name)){
                    return properties[name];
                }
                return null;
            }
            set {
                properties[name] = value;
            }
        }

    }

    class Comparer<T> : IComparer<ObjectWithProperties> where T : IComparable {

        string m_attributeName;

        public Comparer(string attributeName){
            m_attributeName = attributeName;
        }

        public int Compare(ObjectWithProperties x, ObjectWithProperties y) {
            return ((T)x[m_attributeName]).CompareTo((T)y[m_attributeName]);
        }

    }

    class Program {

        static void Main(string[] args) {

            // create some objects and fill a list
            var obj1 = new ObjectWithProperties();
            obj1["test"] = 100;
            var obj2 = new ObjectWithProperties();
            obj2["test"] = 200;
            var obj3 = new ObjectWithProperties();
            obj3["test"] = 150;
            var objects = new List<ObjectWithProperties>(new ObjectWithProperties[]{ obj1, obj2, obj3 });

            // filtering:
            Console.WriteLine("Filtering:");
            var filtered = from obj in objects
                         where (int)obj["test"] >= 150
                         select obj;
            foreach (var obj in filtered){
                Console.WriteLine(obj["test"]);
            }

            // sorting:
            Console.WriteLine("Sorting:");
            Comparer<int> c = new Comparer<int>("test");
            objects.Sort(c);
            foreach (var obj in objects) {
                Console.WriteLine(obj["test"]);
            }
        }

    }
}
Phenylamine answered 3/6, 2009 at 21:8 Comment(0)
T
31

If you need this for data-binding purposes, you can do this with a custom descriptor model... by implementing ICustomTypeDescriptor, TypeDescriptionProvider and/or TypeCoverter, you can create your own PropertyDescriptor instances at runtime. This is what controls like DataGridView, PropertyGrid etc use to display properties.

To bind to lists, you'd need ITypedList and IList; for basic sorting: IBindingList; for filtering and advanced sorting: IBindingListView; for full "new row" support (DataGridView): ICancelAddNew (phew!).

It is a lot of work though. DataTable (although I hate it) is cheap way of doing the same thing. If you don't need data-binding, just use a hashtable ;-p

Here's a simple example - but you can do a lot more...

Tyre answered 3/6, 2009 at 21:11 Comment(3)
thanks... be able to databind directly is what i was looking for. so basically the cheap way to do it is to translate the object collection into DataTable then bind the table instead. i guess there are more things to worry about after the conversion as well.. thanks for you input.Salutation
As a side note, data binding through ICustomTypeDescriptor is not supported by Silverlight :(.Garnish
As a side node to the side note, Silverlight 5 introduced ICustomTypeProvider interface in place of ICustomTypeDescriptor. ICustomTypeProvider was subsequently ported to .NET Framework 4.5, to allow portability between Silverlight and .NET Framework. :).Cockleboat
V
30

Use ExpandoObject like the ViewBag in MVC 3.

Venturous answered 12/8, 2011 at 10:1 Comment(0)
P
12

Create a Hashtable called "Properties" and add your properties to it.

Permissive answered 3/6, 2009 at 21:5 Comment(0)
L
11

I'm not sure you really want to do what you say you want to do, but it's not for me to reason why!

You cannot add properties to a class after it has been JITed.

The closest you could get would be to dynamically create a subtype with Reflection.Emit and copy the existing fields over, but you'd have to update all references to the the object yourself.

You also wouldn't be able to access those properties at compile time.

Something like:

public class Dynamic
{
    public Dynamic Add<T>(string key, T value)
    {
        AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("Dynamic.dll");
        TypeBuilder typeBuilder = moduleBuilder.DefineType(Guid.NewGuid().ToString());
        typeBuilder.SetParent(this.GetType());
        PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(key, PropertyAttributes.None, typeof(T), Type.EmptyTypes);

        MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_" + key, MethodAttributes.Public, CallingConventions.HasThis, typeof(T), Type.EmptyTypes);
        ILGenerator getter = getMethodBuilder.GetILGenerator();
        getter.Emit(OpCodes.Ldarg_0);
        getter.Emit(OpCodes.Ldstr, key);
        getter.Emit(OpCodes.Callvirt, typeof(Dynamic).GetMethod("Get", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(typeof(T)));
        getter.Emit(OpCodes.Ret);
        propertyBuilder.SetGetMethod(getMethodBuilder);

        Type type = typeBuilder.CreateType();

        Dynamic child = (Dynamic)Activator.CreateInstance(type);
        child.dictionary = this.dictionary;
        dictionary.Add(key, value);
        return child;
    }

    protected T Get<T>(string key)
    {
        return (T)dictionary[key];
    }

    private Dictionary<string, object> dictionary = new Dictionary<string,object>();
}

I don't have VS installed on this machine so let me know if there are any massive bugs (well... other than the massive performance problems, but I didn't write the specification!)

Now you can use it:

Dynamic d = new Dynamic();
d = d.Add("MyProperty", 42);
Console.WriteLine(d.GetType().GetProperty("MyProperty").GetValue(d, null));

You could also use it like a normal property in a language that supports late binding (for example, VB.NET)

Limekiln answered 3/6, 2009 at 22:47 Comment(0)
S
4

I have done exactly this with an ICustomTypeDescriptor interface and a Dictionary.

Implementing ICustomTypeDescriptor for dynamic properties:

I have recently had a requirement to bind a grid view to a record object that could have any number of properties that can be added and removed at runtime. This was to allow a user to add a new column to a result set to enter an additional set of data.

This can be achieved by having each data 'row' as a dictionary with the key being the property name and the value being a string or a class that can store the value of the property for the specified row. Of course having a List of Dictionary objects will not be able to be bound to a grid. This is where the ICustomTypeDescriptor comes in.

By creating a wrapper class for the Dictionary and making it adhere to the ICustomTypeDescriptor interface the behaviour for returning properties for an object can be overridden.

Take a look at the implementation of the data 'row' class below:

/// <summary>
/// Class to manage test result row data functions
/// </summary>
public class TestResultRowWrapper : Dictionary<string, TestResultValue>, ICustomTypeDescriptor
{
    //- METHODS -----------------------------------------------------------------------------------------------------------------

    #region Methods

    /// <summary>
    /// Gets the Attributes for the object
    /// </summary>
    AttributeCollection ICustomTypeDescriptor.GetAttributes()
    {
        return new AttributeCollection(null);
    }

    /// <summary>
    /// Gets the Class name
    /// </summary>
    string ICustomTypeDescriptor.GetClassName()
    {
        return null;
    }

    /// <summary>
    /// Gets the component Name
    /// </summary>
    string ICustomTypeDescriptor.GetComponentName()
    {
        return null;
    }

    /// <summary>
    /// Gets the Type Converter
    /// </summary>
    TypeConverter ICustomTypeDescriptor.GetConverter()
    {
        return null;
    }

    /// <summary>
    /// Gets the Default Event
    /// </summary>
    /// <returns></returns>
    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
    {
        return null;
    }

    /// <summary>
    /// Gets the Default Property
    /// </summary>
    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
    {
        return null;
    }

    /// <summary>
    /// Gets the Editor
    /// </summary>
    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return null;
    }

    /// <summary>
    /// Gets the Events
    /// </summary>
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return new EventDescriptorCollection(null);
    }

    /// <summary>
    /// Gets the events
    /// </summary>
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return new EventDescriptorCollection(null);
    }

    /// <summary>
    /// Gets the properties
    /// </summary>
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        List<propertydescriptor> properties = new List<propertydescriptor>();

        //Add property descriptors for each entry in the dictionary
        foreach (string key in this.Keys)
        {
            properties.Add(new TestResultPropertyDescriptor(key));
        }

        //Get properties also belonging to this class also
        PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this.GetType(), attributes);

        foreach (PropertyDescriptor oPropertyDescriptor in pdc)
        {
            properties.Add(oPropertyDescriptor);
        }

        return new PropertyDescriptorCollection(properties.ToArray());
    }

    /// <summary>
    /// gets the Properties
    /// </summary>
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(null);
    }

    /// <summary>
    /// Gets the property owner
    /// </summary>
    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }

    #endregion Methods

    //---------------------------------------------------------------------------------------------------------------------------
}

Note: In the GetProperties method I Could Cache the PropertyDescriptors once read for performance but as I'm adding and removing columns at runtime I always want them rebuilt

You will also notice in the GetProperties method that the Property Descriptors added for the dictionary entries are of type TestResultPropertyDescriptor. This is a custom Property Descriptor class that manages how properties are set and retrieved. Take a look at the implementation below:

/// <summary>
/// Property Descriptor for Test Result Row Wrapper
/// </summary>
public class TestResultPropertyDescriptor : PropertyDescriptor
{
    //- PROPERTIES --------------------------------------------------------------------------------------------------------------

    #region Properties

    /// <summary>
    /// Component Type
    /// </summary>
    public override Type ComponentType
    {
        get { return typeof(Dictionary<string, TestResultValue>); }
    }

    /// <summary>
    /// Gets whether its read only
    /// </summary>
    public override bool IsReadOnly
    {
        get { return false; }
    }

    /// <summary>
    /// Gets the Property Type
    /// </summary>
    public override Type PropertyType
    {
        get { return typeof(string); }
    }

    #endregion Properties

    //- CONSTRUCTOR -------------------------------------------------------------------------------------------------------------

    #region Constructor

    /// <summary>
    /// Constructor
    /// </summary>
    public TestResultPropertyDescriptor(string key)
        : base(key, null)
    {

    }

    #endregion Constructor

    //- METHODS -----------------------------------------------------------------------------------------------------------------

    #region Methods

    /// <summary>
    /// Can Reset Value
    /// </summary>
    public override bool CanResetValue(object component)
    {
        return true;
    }

    /// <summary>
    /// Gets the Value
    /// </summary>
    public override object GetValue(object component)
    {
          return ((Dictionary<string, TestResultValue>)component)[base.Name].Value;
    }

    /// <summary>
    /// Resets the Value
    /// </summary>
    public override void ResetValue(object component)
    {
        ((Dictionary<string, TestResultValue>)component)[base.Name].Value = string.Empty;
    }

    /// <summary>
    /// Sets the value
    /// </summary>
    public override void SetValue(object component, object value)
    {
        ((Dictionary<string, TestResultValue>)component)[base.Name].Value = value.ToString();
    }

    /// <summary>
    /// Gets whether the value should be serialized
    /// </summary>
    public override bool ShouldSerializeValue(object component)
    {
        return false;
    }

    #endregion Methods

    //---------------------------------------------------------------------------------------------------------------------------
}

The main properties to look at on this class are GetValue and SetValue. Here you can see the component being casted as a dictionary and the value of the key inside it being Set or retrieved. Its important that the dictionary in this class is the same type in the Row wrapper class otherwise the cast will fail. When the descriptor is created the key (property name) is passed in and is used to query the dictionary to get the correct value.

Taken from my blog at:

ICustomTypeDescriptor Implementation for dynamic properties

Sulfonal answered 6/1, 2011 at 10:38 Comment(4)
I know you wrote this forever ago, but you really should put some of your code in your answer, or quote something from your post. I think that's in the rules-- your answer becomes almost meaningless if your link were to go dark. Not going to downvote though because you can lookup ICustomTypeDescriptor on MSDN (msdn.microsoft.com/en-us/library/…)Portraitist
@DavidSchwartz - Added.Sulfonal
I have exactly the same design problem as you, this looks like a good solution. Well either this or I do away with databinding and manually control the ui via code behind in my view. Can you do two way binding with this approach?Weintraub
@rolls yes you can, just make sure your property descriptor does not return that its read only. I have used a similar approach recently for something else too that shows the data in a treelist that allows data to be edited in the cellsSulfonal
R
1

I'm not sure what your reasons are, and even if you could pull it off somehow with Reflection Emit (I' not sure that you can), it doesn't sound like a good idea. What is probably a better idea is to have some kind of Dictionary and you can wrap access to the dictionary through methods in your class. That way you can store the data from the database in this dictionary, and then retrieve them using those methods.

Robinett answered 3/6, 2009 at 21:7 Comment(0)
H
1

You should look into DependencyObjects as used by WPF these follow a similar pattern whereby properties can be assigned at runtime. As mentioned above this ultimately points towards using a hash table.

One other useful thing to have a look at is CSLA.Net. The code is freely available and uses some of the principles\patterns it appears you are after.

Also if you are looking at sorting and filtering I'm guessing you're going to be using some kind of grid. A useful interface to implement is ICustomTypeDescriptor, this lets you effectively override what happens when your object gets reflected on so you can point the reflector to your object's own internal hash table.

Hoopla answered 3/6, 2009 at 22:24 Comment(0)
A
1

As a replacement for some of orsogufo's code, because I recently went with a dictionary for this same problem myself, here is my [] operator:

public string this[string key]
{
    get { return properties.ContainsKey(key) ? properties[key] : null; }

    set
    {
        if (properties.ContainsKey(key))
        {
            properties[key] = value;
        }
        else
        {
            properties.Add(key, value);
        }
    }
}

With this implementation, the setter will add new key-value pairs when you use []= if they do not already exist in the dictionary.

Also, for me properties is an IDictionary and in constructors I initialize it to new SortedDictionary<string, string>().

Anu answered 8/7, 2009 at 16:34 Comment(1)
I am trying your solution. i am setting a values at service side as record[name_column] = DBConvert.To<string>(r[name_column]); where record is my DTO. How do i get this value at client side?Scutellation
M
0

Why not use an indexer with the property name as a string value passed to the indexer?

Monomial answered 3/6, 2009 at 21:5 Comment(0)
F
0

Couldn't you just have your class expose a Dictionary object? Instead of "attaching more properties to the object", you could simply insert your data (with some identifier) into the dictionary at run time.

Freaky answered 3/6, 2009 at 21:8 Comment(0)
G
0

If it is for binding, then you can reference indexers from XAML

Text="{Binding [FullName]}"

Here it is referencing the class indexer with the key "FullName"

Goy answered 14/4, 2011 at 21:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.