如何在C#中创builddynamic属性?

我正在寻找一种方法来创build一个具有一组静态属性的类。 在运行时,我希望能够从数据库中为这个对象添加其他的dynamic属性。 我也想为这些对象添加sorting和过滤function。

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

你可以用字典说

Dictionary<string,object> properties; 

我认为在大多数情况下,类似的事情就这样完成了。
在任何情况下,你都不会从set和get访问器创build一个“真正的”属性中获得任何东西,因为它只会在运行时创build,并且不会在代码中使用它。

下面是一个例子,展示了一个可能的过滤和sorting实现(没有错误检查):

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

如果你需要这样的数据绑定的目的,你可以做一个自定义描述符模型…通过实现ICustomTypeDescriptorTypeDescriptionProvider和/或TypeCoverter ,你可以在运行时创build自己的PropertyDescriptor实例。 这就是像DataGridViewPropertyGrid等用来显示属性的控件。

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

虽然这是很多工作。 DataTable (虽然我讨厌它)是做同样的事情的便宜的方式。 如果你不需要数据绑定,只需使用散列表; -p

这里有一个简单的例子 – 但你可以做更多的事情…

像MVC 3中的ViewBag一样使用ExpandoObject 。

创build一个名为“属性”的哈希表,并添加你的属性。

我不确定你是否真的想做你想说的事情 ,但是为什么呢?

已经被JIT控制的类不能添加属性。

最近你可能会dynamic地创build一个带有Reflection.Emit的子types并复制现有的字段,但是你必须自己更新对这个对象的所有引用。

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

就像是:

 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)的普通属性一样使用它。

我已经完成了ICustomTypeDescriptor接口和一个字典。

为dynamic属性实现ICustomTypeDescriptor:

我最近有一个要求绑定一个网格视图的logging对象,可以有任何数量的属性,可以在运行时添加和删除。 这是为了让用户添加一个新的列到结果集来input一组额外的数据。

这可以通过将每个数据“行”作为字典来实现,其中键是属性名,并且该值是可以存储指定行的属性值的string或类。 当然,有一个字典对象列表将不能被绑定到一个网格。 这是ICustomTypeDescriptor进来的地方。

通过为Dictionary创build一个包装类并使其符合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方法中,我可以高速cachingPropertyDescriptors的性能,但由于我在运行时添加和删除列,我总是希望它们重build

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

 /// <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包装类中的相同types,否则转换将失败。 当描述符被创build时,键(属性名)被传入并用于查询字典以获得正确的值。

取自我的博客:

ICustomTypeDescriptordynamic属性的实现

我不确定你的理由是什么,即使你可以用Reflection Emit(我不确定你是否可以)把它关掉,但这听起来不是一个好主意。 可能更好的办法是拥有某种字典,并且可以通过类中的方法来包装对字典的访问。 这样,您可以将数据库中的数据存储在此字典中,然后使用这些方法检索它们。

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

另外一个有用的东西是CSLA.Net 。 代码是免费的,并使用了一些你看起来以后的原则和模式。

另外,如果你正在寻找sorting和筛选,我猜你会使用某种网格。 一个有用的实现接口是ICustomTypeDescriptor,这可以让你有效地覆盖当你的对象被reflection时发生的事情,所以你可以将reflection器指向你的对象自己的内部哈希表。

作为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<string, string>()

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

如果是绑定,那么可以引用来自XAML的索引器

 Text="{Binding [FullName]}" 

在这里它引用类索引器的关键字“FullName”

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