【问题标题】:How do I create dynamic properties in C#?如何在 C# 中创建动态属性?
【发布时间】:2010-10-31 03:22:17
【问题描述】:

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

如何在 C# 中做到这一点?

【问题讨论】:

  • 这个类的目的是什么?您的请求让我怀疑您确实需要设计模式或其他东西,尽管不知道您的用例是什么意味着我实际上没有建议。

标签: c#


【解决方案1】:

你可能会使用字典,比如说

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

    }
}

【讨论】:

    【解决方案2】:

    如果您需要将其用于数据绑定目的,您可以使用自定义描述符模型来实现...通过实现 ICustomTypeDescriptorTypeDescriptionProvider 和/或 TypeCoverter,您可以创建自己的 PropertyDescriptor 实例在运行时。这就是 DataGridViewPropertyGrid 等控件用于显示属性的内容。

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

    这是一个很多的工作。 DataTable(虽然我讨厌它)是做同样事情的廉价方式。如果您不需要数据绑定,只需使用哈希表;-p

    这是simple example - 但您可以做更多...

    【讨论】:

    • 谢谢...能够直接进行数据绑定是我一直在寻找的。所以基本上便宜的方法是将对象集合转换为 DataTable 然后绑定表。我想在转换之后还有更多需要担心的事情。感谢您的输入。
    • 附带说明,Silverlight 不支持通过 ICustomTypeDescriptor 进行数据绑定 :(.
    • 作为旁注的旁节点,Silverlight 5 引入了 ICustomTypeProvider 接口来代替 ICustomTypeDescriptor。 ICustomTypeProvider 随后被移植到 .NET Framework 4.5,以允许 Silverlight 和 .NET Framework 之间的可移植性。 :)。
    【解决方案3】:

    像 MVC 3 中的 ViewBag 一样使用 ExpandoObject

    【讨论】:

      【解决方案4】:

      创建一个名为“Properties”的 Hashtable 并将您的属性添加到其中。

      【讨论】:

        【解决方案5】:

        我不确定你真的想做你说你想做的事,但我无法解释为什么!

        在 JITed 之后,您不能将属性添加到类。

        最接近的方法是使用 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)中像普通属性一样使用它

        【讨论】:

          【解决方案6】:

          我已经使用 ICustomTypeDescriptor 接口和字典完成了这项工作。

          为动态属性实现 ICustomTypeDescriptor:

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

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

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

          看看下面数据'row'类的实现:

          /// <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 Implementation for dynamic properties

          【讨论】:

          • 我知道你很久以前就写过这个,但你真的应该把你的一些代码放在你的答案中,或者从你的帖子中引用一些东西。我认为这是规则 - 如果您的链接变暗,您的答案将变得几乎毫无意义。不过不会投反对票,因为您可以在 MSDN 上查找 ICustomTypeDescriptor (msdn.microsoft.com/en-us/library/…)
          • @DavidSchwartz - 已添加。
          • 我的设计问题和你一模一样,这看起来是个不错的解决方案。好吧,或者我取消数据绑定并通过我认为后面的代码手动控制 ui。你能用这种方法做双向绑定吗?
          • @rolls 是的,你可以,只要确保你的属性描述符不返回它的只读。我最近也使用了类似的方法来处理其他事情,它在树形列表中显示数据,允许在单元格中编辑数据
          【解决方案7】:

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

          另一个有用的东西是CSLA.Net。该代码是免费提供的,并使用了您所追求的一些原则\模式。

          此外,如果您正在研究排序和过滤,我猜您将使用某种网格。一个有用的接口是 ICustomTypeDescriptor,它可以让你有效地覆盖当你的对象被反射时发生的事情,这样你就可以将反射器指向你的对象自己的内部哈希表。

          【讨论】:

            【解决方案8】:

            作为一些 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);
                    }
                }
            }
            

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

            另外,对我来说properties 是一个IDictionary,在构造函数中我将它初始化为new SortedDictionary&lt;string, string&gt;()

            【讨论】:

            • 我正在尝试您的解决方案。我将服务端的值设置为record[name_column] = DBConvert.To&lt;string&gt;(r[name_column]);,其中record 是我的DTO。我如何在客户端获得这个值?
            【解决方案9】:

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

            【讨论】:

              【解决方案10】:

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

              【讨论】:

                【解决方案11】:

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

                【讨论】:

                  【解决方案12】:

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

                  Text="{Binding [FullName]}"
                  

                  这里是使用键“FullName”引用类索引器

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-07-03
                    • 2012-02-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多