[英]How do I create dynamic properties in C#?
我正在尋找一種方法來創建具有一組靜態屬性的類。 在運行時,我希望能夠從數據庫向該對象添加其他動態屬性。 我還想為這些對象添加排序和過濾功能。
我如何在 C# 中做到這一點?
你可能會使用字典,說
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"]);
}
}
}
}
如果您出於數據綁定的目的需要這樣做,您可以使用自定義描述符模型來實現……通過實現ICustomTypeDescriptor
、 TypeDescriptionProvider
和/或TypeCoverter
,您可以在運行時創建自己的PropertyDescriptor
實例。 這就是DataGridView
、 PropertyGrid
等控件用來顯示屬性的內容。
要綁定到列表,您需要ITypedList
和IList
; 基本排序: IBindingList
; 用於過濾和高級排序: IBindingListView
; 完整的“新行”支持( DataGridView
): ICancelAddNew
( ICancelAddNew
!)。
雖然這是很多工作。 DataTable
(雖然我討厭它)是做同樣事情的廉價方式。 如果您不需要數據綁定,只需使用哈希表 ;-p
這是一個簡單的例子- 但你可以做更多......
像 MVC 3 中的 ViewBag 一樣使用ExpandoObject 。
創建一個名為“屬性”的哈希表並將您的屬性添加到其中。
我不確定你真的想做你說你想做的事,但我不知道為什么!
在類被 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)中的普通屬性一樣使用它
我已經使用 ICustomTypeDescriptor 接口和字典完成了這項工作。
為動態屬性實現 ICustomTypeDescriptor:
我最近需要將網格視圖綁定到一個記錄對象,該對象可以具有任意數量的可以在運行時添加和刪除的屬性。 這是為了允許用戶向結果集添加新列以輸入額外的數據集。
這可以通過將每個數據“行”作為字典來實現,鍵是屬性名稱,值是字符串或可以存儲指定行的屬性值的類。 當然,擁有 Dictionary 對象列表將無法綁定到網格。 這就是 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 類型。 這是一個自定義屬性描述符類,用於管理如何設置和檢索屬性。 看看下面的實現:
/// <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 包裝器類中的類型相同很重要,否則轉換將失敗。 創建描述符時,鍵(屬性名稱)被傳入並用於查詢字典以獲得正確的值。
摘自我的博客:
作為一些 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>()
。
我不確定您的原因是什么,即使您可以通過 Reflection Emit 以某種方式實現它(我不確定您是否可以),這聽起來也不是一個好主意。 可能更好的主意是擁有某種字典,您可以通過類中的方法包裝對字典的訪問。 這樣您就可以將數據庫中的數據存儲在此字典中,然后使用這些方法檢索它們。
您應該查看 WPF 使用的 DependencyObjects,它們遵循類似的模式,可以在運行時分配屬性。 如上所述,這最終指向使用哈希表。
另一個有用的東西是CSLA.Net 。 該代碼是免費提供的,並使用了您所追求的一些原則\\模式。
此外,如果您正在查看排序和過濾,我猜您將使用某種網格。 要實現的一個有用接口是 ICustomTypeDescriptor,它可以讓您有效地覆蓋當您的對象被反射時發生的事情,以便您可以將反射器指向您的對象自己的內部哈希表。
如果是用於綁定,那么您可以從 XAML 引用索引器
Text="{Binding [FullName]}"
這里它使用鍵“FullName”引用類索引器
為什么不使用帶有屬性名稱的索引器作為傳遞給索引器的字符串值?
你不能讓你的類公開一個 Dictionary 對象嗎? 您可以簡單地在運行時將數據(帶有一些標識符)插入到字典中,而不是“向對象附加更多屬性”。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.