How do I create dynamic properties in C#?

2019-01-02 14:45发布

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#?

标签: c#
12条回答
栀子花@的思念
2楼-- · 2019-01-02 14:54

Use ExpandoObject like the ViewBag in MVC 3.

查看更多
路过你的时光
3楼-- · 2019-01-02 14:56

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"]);
            }
        }

    }
}
查看更多
步步皆殇っ
4楼-- · 2019-01-02 14:56

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"

查看更多
步步皆殇っ
5楼-- · 2019-01-02 15:00

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

查看更多
无色无味的生活
6楼-- · 2019-01-02 15:01

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>().

查看更多
与君花间醉酒
7楼-- · 2019-01-02 15:05

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)

查看更多
登录 后发表回答