簡體   English   中英

如何在 C# 中創建動態屬性?

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

    }
}

如果您出於數據綁定的目的需要這樣做,您可以使用自定義描述符模型來實現……通過實現ICustomTypeDescriptorTypeDescriptionProvider和/或TypeCoverter ,您可以在運行時創建自己的PropertyDescriptor實例。 這就是DataGridViewPropertyGrid等控件用來顯示屬性的內容。

要綁定到列表,您需要ITypedListIList 基本排序: IBindingList 用於過濾和高級排序: IBindingListView 完整的“新行”支持( DataGridView ): ICancelAddNewICancelAddNew !)。

雖然這是很多工作。 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 包裝器類中的類型相同很重要,否則轉換將失敗。 創建描述符時,鍵(屬性名稱)被傳入並用於查詢字典以獲得正確的值。

摘自我的博客:

動態屬性的 ICustomTypeDescriptor 實現

作為一些 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM