如何在 C# 中创建动态属性?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/947241/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I create dynamic properties in C#?
提问by Eatdoku
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# 中做到这一点?
采纳答案by Paolo Tedesco
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...
我认为在大多数情况下,在完成类似的事情时,都是这样完成的。
在任何情况下,您都不会从使用 set 和 get 访问器创建“真实”属性中获得任何好处,因为它只会在运行时创建,而您不会在代码中使用它...
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"]);
}
}
}
}
回答by Aric TenEyck
Create a Hashtable called "Properties" and add your properties to it.
创建一个名为“属性”的哈希表并将您的属性添加到其中。
回答by Randolpho
Why not use an indexer with the property name as a string value passed to the indexer?
为什么不使用带有属性名称的索引器作为传递给索引器的字符串值?
回答by BFree
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.
我不确定您的原因是什么,即使您可以通过 Reflection Emit 以某种方式实现它(我不确定您是否可以),这听起来也不是一个好主意。可能更好的主意是拥有某种字典,您可以通过类中的方法包装对字典的访问。这样您就可以将数据库中的数据存储在此字典中,然后使用这些方法检索它们。
回答by Rob
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.
你不能让你的类公开一个 Dictionary 对象吗?您可以简单地在运行时将数据(带有一些标识符)插入到字典中,而不是“向对象附加更多属性”。
回答by Marc Gravell
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.
如果您出于数据绑定的目的需要这样做,您可以使用自定义描述符模型...通过实现ICustomTypeDescriptor
,TypeDescriptionProvider
和/或TypeCoverter
,您可以PropertyDescriptor
在运行时创建自己的实例。这就是诸如DataGridView
、PropertyGrid
等控件用来显示属性的内容。
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!).
要绑定到列表,您需要ITypedList
and IList
; 为基本排序:IBindingList
; 用于过滤和高级排序:IBindingListView
; 对于完整的“新行”支持(DataGridView
):(ICancelAddNew
呸!)。
It is a lotof 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
虽然这是很多工作。DataTable
(虽然我讨厌它)是做同样事情的廉价方式。如果您不需要数据绑定,只需使用哈希表 ;-p
Here's a simple example- but you can do a lot more...
这是一个简单的例子- 但你可以做更多......
回答by gsobocinski
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.
您应该查看 WPF 使用的 DependencyObjects,它们遵循类似的模式,可以在运行时分配属性。如上所述,这最终指向使用哈希表。
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.
另一个有用的东西是CSLA.Net。该代码是免费提供的,并使用了您所追求的一些原则\模式。
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.
此外,如果您正在查看排序和过滤,我猜您将使用某种网格。要实现的一个有用接口是 ICustomTypeDescriptor,它可以让您有效地覆盖当您的对象被反射时发生的事情,以便您可以将反射器指向您的对象自己的内部哈希表。
回答by Alun Harford
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.
在类被 JIT 之后,您不能向类添加属性。
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.
您可以获得的最接近的方法是使用 Reflection.Emit 动态创建子类型并复制现有字段,但您必须自己更新对对象的所有引用。
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!)
我没有在这台机器上安装 VS,所以如果有任何严重的错误,请告诉我(嗯......除了大量的性能问题,但我没有写规范!)
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)
您还可以像使用支持后期绑定的语言(例如,VB.NET)中的普通属性一样使用它
回答by Sarah Vessels
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:
作为一些 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);
}
}
}
With this implementation, the setter will add new key-value pairs when you use []=
if they do not already exist in the dictionary.
使用此实现,[]=
如果字典中尚不存在,setter 将在您使用时添加新的键值对。
Also, for me properties
is an IDictionary
and in constructors I initialize it to new SortedDictionary<string, string>()
.
另外,对我来说properties
是一个IDictionary
并且在构造函数中我将它初始化为new SortedDictionary<string, string>()
.
回答by WraithNath
I have done exactly this with an ICustomTypeDescriptor interface and a Dictionary.
我已经使用 ICustomTypeDescriptor 接口和字典完成了这项工作。
Implementing ICustomTypeDescriptor for dynamic properties:
为动态属性实现 ICustomTypeDescriptor:
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.
这可以通过将每个数据“行”作为字典来实现,键是属性名称,值是字符串或可以存储指定行的属性值的类。当然,拥有 Dictionary 对象列表将无法绑定到网格。这就是 ICustomTypeDescriptor 的用武之地。
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.
通过为 Dictionary 创建包装类并使其遵循 ICustomTypeDescriptor 接口,可以覆盖返回对象属性的行为。
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
注意:在 GetProperties 方法中,我可以缓存 PropertyDescriptors 一次读取以提高性能,但是当我在运行时添加和删除列时,我总是希望它们重建
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:
您还会注意到在 GetProperties 方法中为字典条目添加的属性描述符是 TestResultPropertyDescriptor 类型。这是一个自定义属性描述符类,用于管理如何设置和检索属性。看看下面的实现:
/// <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.
要查看此类的主要属性是 GetValue 和 SetValue。在这里,您可以看到组件被转换为字典,并且其中的键值被设置或检索。此类中的字典与 Row 包装器类中的类型相同很重要,否则转换将失败。创建描述符时,键(属性名称)被传入并用于查询字典以获得正确的值。
Taken from my blog at:
摘自我的博客: