如何在C#中创建动态属性?


87

我正在寻找一种创建具有一组静态属性的类的方法。在运行时,我希望能够从数据库中向该对象添加其他动态属性。我还想为这些对象添加排序和过滤功能。

如何在C#中执行此操作?


3
本课程的目的是什么?您的请求使我怀疑您是否确实需要设计模式或其他内容,尽管不知道用例是什么,但实际上我没有任何建议。
布赖恩2009年

Answers:


59

您可能会使用字典,例如

Dictionary<string,object> properties;

我认为在大多数情况下,类似的事情都是这样完成的。
无论如何,使用set和get访问器创建“真实”属性不会获得任何好处,因为它将仅在运行时创建,并且您不会在代码中使用它。

这是一个示例,显示了可能的过滤和排序实现(无错误检查):

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

    }
}

30

如果您需要这个数据绑定的目的,你可以使用自定义的描述模型做到这一点......通过实施ICustomTypeDescriptorTypeDescriptionProvider和/或TypeCoverter,你可以创建自己的PropertyDescriptor在运行时的情况。这是什么样的控制一样DataGridViewPropertyGrid等使用,以显示属性。

要绑定到列表,您需要ITypedListIList;为基本排序:IBindingList; 用于过滤和高级排序:IBindingListView; 以获得完整的“新行”支持(DataGridView):(ICancelAddNewphe!)。

虽然这是很多工作。DataTable(尽管我讨厌)做同一件事的便宜方法。如果不需要数据绑定,只需使用哈希表;-p

这是一个简单的示例-但您可以做更多的事情...


谢谢...能够直接进行数据绑定是我一直在寻找的东西。因此,基本上,最便宜的方法是将对象集合转换为DataTable然后绑定表。我想转换后还有更多的事情要担心..谢谢您的输入。
Eatdoku

作为边注,通过数据绑定ICustomTypeDescriptor不Silverlight支持:(。
简略Hagenlocher

作为旁注的补充,Silverlight 5引入了ICustomTypeProvider接口来代替ICustomTypeDescriptor。ICustomTypeProvider随后被移植到.NET Framework 4.5,以允许Silverlight和.NET Framework之间的可移植性。:)。
爱德华



12

我不确定您是否确实想做您想做的事情,但这不是我要理由!

JIT后,您不能将属性添加到类。

您可能获得的最接近的结果是使用Reflection.Emit动态创建一个子类型并复制现有字段,但是您必须自己更新对该对象的所有引用。

您也将无法在编译时访问这些属性。

就像是:

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>();
}

我没有在这台机器上安装VS,所以让我知道是否有大量错误(嗯……除了大量的性能问题外,我没有写规范!)

现在您可以使用它:

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

您还可以像支持延迟绑定的语言(例如,VB.NET)中的普通属性一样使用它。


4

我已经使用ICustomTypeDescriptor接口和Dictionary完成了此操作。

为动态属性实现ICustomTypeDescriptor:

最近,我需要将网格视图绑定到记录对象,该记录对象可以具有在运行时可以添加和删除的任意数量的属性。这是为了允许用户向结果集中添加新列,以输入其他数据集。

这可以通过将每个数据“行”作为字典来实现,其中键是属性名,值是可以存储指定行的属性值的字符串或类。当然,具有“字典列表”对象将无法绑定到网格。这是ICustomTypeDescriptor的来源。

通过为Dictionary创建包装类并使其遵循ICustomTypeDescriptor接口,可以覆盖返回对象属性的行为。

看一下下面的数据“行”类的实现:

/// <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

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

注意:在GetProperties方法中,我可以缓存读取的PropertyDescriptors以提高性能,但是当我在运行时添加和删除列时,我总是希望重建它们

您还将在GetProperties方法中注意到,为字典条目添加的属性描述符的类型为TestResultPropertyDescriptor。这是一个自定义的Property Descriptor类,用于管理如何设置和检索属性。看一下下面的实现:

/// <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

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

在此类上要查看的主要属性是GetValue和SetValue。在这里,您可以看到该组件被转换为词典,并且其中的键值被设置或检索。重要的是,此类中的字典与Row包装类中的类型相同,否则强制转换将失败。创建描述符时,将传递密钥(属性名称),并用于查询字典以获取正确的值。

取自我的博客,网址为:

ICustomTypeDescriptor实现的动态属性


我知道您是永远写这篇文章的,但是您确实应该在答案中添加一些代码,或者引用您的帖子中的内容。我认为这是有规律的-如果您的链接不可用,您的答案几乎变得毫无意义。但是,由于您可以在MSDN上查找ICustomTypeDescriptor(msdn.microsoft.com/en-us/library/…
David Schwartz

@DavidSchwartz-添加。
WraithNath '16

我有与您完全相同的设计问题,这看起来是个不错的解决方案。好吧,这或者我取消了数据绑定,并通过视图中的代码手动控制ui。您可以使用这种方法进行两种方式的绑定吗?

@rolls是的,您可以,只需确保您的属性描述符不返回只读属性即可。我最近在其他方面也使用了类似的方法,即在树形列表中显示数据,从而可以在单元格中编辑数据
WraithNath 2016年

1

您应该查看WPF使用的DependencyObject,它们遵循类似的模式,可以在运行时分配属性。如上所述,这最终指向使用哈希表。

另外一个有用的东西是CSLA.Net。该代码是免费提供的,并使用您遵循的某些原理\模式。

另外,如果您正在查看排序和过滤功能,那么我猜您将使用某种网格。ICustomTypeDescriptor是一个有用的实现接口,它使您可以有效地覆盖对象被反射时发生的情况,从而可以将反射器指向对象自己的内部哈希表。


1

作为orsogufo的某些代码的替代,因为我最近本人也使用字典解决了同样的问题,所以这里是我的[]运算符:

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

通过此实现,当您使用新的键值对([]=如果字典中尚不存在)时,设置器将添加它们。

另外,对我来说propertiesIDictionary,在构造函数中,我将其初始化为new SortedDictionary<string, string>()


我正在尝试您的解决方案。我在服务端设置值,因为我的DTOrecord[name_column] = DBConvert.To<string>(r[name_column]);在哪里record。我如何在客户端获得此价值?
Rohaan

1

我不确定您的原因是什么,即使您可以使用Reflection Emit(我不确定也可以)将其实现,但这听起来也不是一个好主意。最好使用某种Dictionary,然后可以通过类中的方法包装对字典的访问。这样,您可以将数据库中的数据存储在此字典中,然后使用这些方法进行检索。


0

为什么不使用带有属性名称的索引器作为传递给索引器的字符串值?


0

您不能只让您的类公开一个Dictionary对象吗?代替“向对象附加更多属性”,您可以在运行时简单地将数据(带有某些标识符)插入字典中。


0

如果用于绑定,则可以从XAML引用索引器

Text="{Binding [FullName]}"

在这里,它使用键“ FullName”引用类索引器

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.