Як створити динамічні властивості в C #?


87

Я шукаю спосіб створити клас із набором статичних властивостей. Під час виконання я хочу мати можливість додавати інші динамічні властивості до цього об’єкта з бази даних. Я також хотів би додати можливості сортування та фільтрації до цих об’єктів.

Як це зробити в C #?


3
Яка мета цього заняття? Ваш запит викликає у мене підозру щодо того, що вам дійсно потрібен зразок дизайну чи щось інше, хоча невідомість того, що ви використовуєте, означає, що я насправді не маю пропозиції.
Брайан

Відповіді:


59

Скажімо, ви можете скористатися словником

Dictionary<string,object> properties;

Я думаю, що в більшості випадків, коли робиться щось подібне, це робиться так.
У будь-якому випадку, ви нічого не отримаєте від створення "реального" властивості за допомогою set і get accessors, оскільки воно буде створене лише під час виконання, і ви не будете використовувати його у своєму коді ...

Ось приклад, що показує можливу реалізацію фільтрації та сортування (без перевірки помилок):

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

    }
}

30

Якщо вам це потрібно для цілей прив'язки даних, ви можете зробити це за допомогою користувальницької моделі дескриптора ... шляхом реалізації ICustomTypeDescriptor, TypeDescriptionProviderі / або TypeCoverter, ви можете створювати свої власні PropertyDescriptorекземпляри під час виконання. Це те , що контроль подобається DataGridView, і PropertyGridт.д. використовувати для відображення властивостей.

Для прив’язки до списків вам знадобиться ITypedListта IList; для базового сортування: IBindingList; для фільтрації та передовий сортування: IBindingListView; для повної підтримки "нового рядка" ( DataGridView): ICancelAddNew(ну!).

Праці це багато . DataTable(хоча я це ненавиджу) - це дешевий спосіб зробити те саме. Якщо вам не потрібна прив'язка даних, просто скористайтесь хеш-таблицею ;-p

Ось простий приклад - але ви можете зробити набагато більше ...


спасибі ... мати можливість безпосередньо прив'язувати дані - це те, що я шукав. тому в основному дешевий спосіб зробити це перевести колекцію об’єктів у DataTable, а потім прив’язати таблицю замість цього. я здогадуюсь є більше речей, про які слід турбуватися після перетворення, а також .. спасибі за вступ.
Eatdoku

Як зауваження, прив'язка даних через ICustomTypeDescriptor не підтримується Silverlight :(.
Curt Hagenlocher

Як побічний вузол до бічної ноти Silverlight 5 представив інтерфейс ICustomTypeProvider замість ICustomTypeDescriptor. Згодом ICustomTypeProvider було перенесено на .NET Framework 4.5, щоб забезпечити переносимість між Silverlight та .NET Framework. :).
Едвард


12

Створіть Hashtable під назвою "Властивості" та додайте до нього свої властивості.


12

Я не впевнений, що ти справді хочеш робити те, що ти кажеш, що хочеш робити , але це не я, щоб міркувати чому!

Ви не можете додавати властивості до класу після його JITed.

Найближчим, що ви могли б отримати, було б динамічно створити підтип за допомогою Reflection. Видаліть і скопіюйте наявні поля, але вам доведеться самостійно оновити всі посилання на об’єкт.

Ви також не зможете отримати доступ до цих властивостей під час компіляції.

Щось на зразок:

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)


4

Я зробив саме це за допомогою інтерфейсу ICustomTypeDescriptor та словника.

Реалізація ICustomTypeDescriptor для динамічних властивостей:

Нещодавно у мене була вимога прив’язати подання сітки до об’єкту запису, який може мати будь-яку кількість властивостей, які можна додати та видалити під час виконання. Це мало дозволити користувачеві додати новий стовпець до набору результатів, щоб ввести додатковий набір даних.

Цього можна досягти, маючи кожен рядок даних як словник, причому ключем є ім'я властивості, а значенням є рядок або клас, який може зберігати значення властивості для зазначеного рядка. Звичайно, наявність списку об’єктів словника неможливо буде прив’язати до сітки. Тут з’являється ICustomTypeDescriptor.

Створюючи обгортковий клас для Словника та змушуючи його дотримуватися інтерфейсу 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. Це власний клас 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. Тут ви можете побачити компонент, який відливається як словник, і значення ключа всередині нього, який встановлюється або отримується. Важливо, щоб словник у цьому класі був однотипним у класі обгортки рядків, інакше приведення не вдасться. Коли дескриптор створений, ключ (ім'я властивості) передається і використовується для запиту словника, щоб отримати правильне значення.

Взято з мого блогу за адресою:

Впровадження ICustomTypeDescriptor для динамічних властивостей


Я знаю, що ви писали це назавжди тому, але вам дійсно слід ввести частину свого коду у свою відповідь або цитувати щось із вашого допису. Я думаю, що це в правилах - ваша відповідь стає майже безглуздою, якби ваше посилання стало темним. Не збираюся голосувати проти, оскільки ви можете шукати ICustomTypeDescriptor на MSDN ( msdn.microsoft.com/en-us/library/… )
Девід Шварц

@DavidSchwartz - Додано.
WraithNath

У мене точно така ж проблема з дизайном, як і у вас, це виглядає як хороше рішення. Ну, або це, або я позбавляюся прив'язки даних і вручну управляю користувацьким інтерфейсом за допомогою коду, на мій погляд. Чи можете ви зробити двосторонній зв'язок із цим підходом?
котиться

@rolls так, ви можете, просто переконайтесь, що ваш дескриптор властивості не повертає, що його лише для читання. Нещодавно я використовував подібний підхід і для чогось іншого, що відображає дані в списку трелістів, що дозволяє редагувати дані в клітинках
WraithNath

1

Ви повинні вивчити DependencyObjects, як їх використовує WPF, вони мають подібний зразок, згідно з яким властивості можуть бути призначені під час виконання. Як зазначалося вище, це в кінцевому рахунку вказує на використання хеш-таблиці.

Ще одна корисна річ, на яку можна поглянути - це CSLA.Net . Код є у вільному доступі та використовує деякі принципи \ моделі, які, як видається, переслідуєте.

Також якщо ви шукаєте сортування та фільтрування, я здогадуюсь, що ви будете використовувати якусь сітку. Корисним інтерфейсом для реалізації є ICustomTypeDescriptor, це дозволяє вам ефективно перевизначити те, що відбувається, коли ваш об'єкт відображається, щоб ви могли направити рефлектор на власну внутрішню хеш-таблицю вашого об'єкта.


1

Як заміну коду 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);
        }
    }
}

За допомогою цієї реалізації сеттер додасть нові пари ключ-значення, коли ви їх використовуєте, []=якщо вони ще не існують у словнику.

Крім того, для мене propertiesє IDictionaryі в конструкторах, я його ініціалізую new SortedDictionary<string, string>().


Я намагаюся ваше рішення. Я встановлюю значення на стороні обслуговування, record[name_column] = DBConvert.To<string>(r[name_column]);де recordзнаходиться моя DTO. Як отримати це значення на стороні клієнта?
Рохаан,

1

Я не впевнений, у чому ваші причини, і навіть якщо б ви могли це якось здійснити за допомогою Reflection Emit (я не впевнений, що можете), це не звучить як гарна ідея. Напевно, краща ідея - це мати якийсь словник, і ви можете обернути доступ до словника методами у своєму класі. Таким чином ви можете зберігати дані з бази даних у цьому словнику, а потім отримувати їх за допомогою цих методів.


0

Чому б не використовувати індексатор з назвою властивості як значення рядка, передане індексатору?


0

Чи не могли б ви просто дати своєму класу виставити об’єкт Словник? Замість того, щоб «додавати до об’єкта більше властивостей», ви можете просто вставити свої дані (з певним ідентифікатором) у словник під час виконання.


0

Якщо це для прив'язки, то ви можете посилатися на індексатори з XAML

Text="{Binding [FullName]}"

Тут він посилається на індексатор класу з ключем "Повне Ім'я"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.