C # 'da nasıl dinamik özellikler oluşturabilirim?


87

Statik özelliklere sahip bir sınıf oluşturmanın bir yolunu arıyorum. Çalışma zamanında, veritabanından bu nesneye başka dinamik özellikler ekleyebilmek istiyorum. Ayrıca bu nesnelere sıralama ve filtreleme yetenekleri eklemek istiyorum.

Bunu C # ile nasıl yaparım?


3
Bu sınıfın amacı nedir? İsteğiniz, gerçekten bir tasarım modeline veya başka bir şeye ihtiyacınız olduğundan şüpheleniyor, ancak kullanım durumunuzun ne olduğunu bilmemek aslında bir önerim olmadığı anlamına geliyor.
Brian

Yanıtlar:


60

Bir sözlük kullanabilirsin diyebilirsin

Dictionary<string,object> properties;

Sanırım benzer bir şeyin yapıldığı çoğu durumda, bu şekilde yapılır.
Her durumda, set ve get erişimcileri ile "gerçek" bir özellik oluşturmaktan hiçbir şey kazanmazsınız, çünkü yalnızca çalışma zamanında oluşturulacak ve kodunuzda kullanmayacaksınız ...

Aşağıda, olası bir filtreleme ve sıralama uygulamasını gösteren bir örnek yer almaktadır (hata denetimi yoktur):

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

Eğer veri bağlama amaçlar için bu gerekiyorsa, ... Özel bir açıklayıcısı modeli ile bunu uygulayarak yapabilirsiniz ICustomTypeDescriptor, TypeDescriptionProviderve / veya TypeCoverterkendi oluşturabilir, PropertyDescriptorçalışma zamanında örnekleri. Bu DataGridView, PropertyGridvb. Kontrollerin özellikleri görüntülemek için kullandığı şeydir .

Listelere bağlanmak için ihtiyacınız olan ITypedListve IList; temel sıralama için IBindingList:; filtreleme ve gelişmiş sıralama için IBindingListView:; tam "yeni sıra" desteği için ( DataGridView): ICancelAddNew(phew!).

Yine de çok iş var. DataTable(ondan nefret etmeme rağmen) aynı şeyi yapmanın ucuz yolu. Veri bağlamaya ihtiyacınız yoksa, sadece bir hashtable kullanın ;-p

İşte basit bir örnek - ama çok daha fazlasını yapabilirsiniz ...


teşekkürler ... aradığım şey doğrudan veri bağlantısı yapabilmek. yani temelde bunu yapmanın ucuz yolu, nesne koleksiyonunu DataTable'a çevirip ardından tabloyu bağlamaktır. sanırım dönüşümden sonra da endişelenecek daha çok şey var .. girdiniz için teşekkürler.
Eatdoku

Bir yan not olarak, ICustomTypeDescriptor aracılığıyla veri bağlama Silverlight tarafından desteklenmemektedir :(.
Curt Hagenlocher,

Silverlight 5, yan nota bir yan düğüm olarak, ICustomTypeDescriptor yerine ICustomTypeProvider arabirimini tanıttı. ICustomTypeProvider, Silverlight ve .NET Framework arasında taşınabilirlik sağlamak için sonradan .NET Framework 4.5'e taşındı. :).
Edward


12

"Özellikler" adlı bir Hashtable oluşturun ve özelliklerinizi buna ekleyin.


12

Yapmak istediğini söylediğin şeyi gerçekten yapmak isteyip istemediğinden emin değilim , ama nedeni bana göre değil!

JIT uygulandıktan sonra bir sınıfa özellik ekleyemezsiniz.

En yakın elde edebileceğiniz şey, Reflection ile dinamik olarak bir alt tür oluşturmaktır. Var olan alanları atıp kopyalayın, ancak nesneye yönelik tüm referansları kendiniz güncellemeniz gerekir.

Ayrıca bu özelliklere derleme zamanında erişemezsiniz.

Gibi bir şey:

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>();
}

Bu makinede VS yüklü değil, bu yüzden herhangi bir büyük hata varsa bana bildirin (şey ... büyük performans sorunları dışında, ancak belirtimi yazmadım!)

Şimdi kullanabilirsiniz:

Dynamic d = new Dynamic();
d = d.Add("MyProperty", 42);
Console.WriteLine(d.GetType().GetProperty("MyProperty").GetValue(d, null));

Geç bağlamayı destekleyen bir dilde normal bir özellik gibi de kullanabilirsiniz (örneğin, VB.NET)


4

Bunu tam olarak bir ICustomTypeDescriptor arayüzü ve bir Sözlük ile yaptım.

Dinamik özellikler için ICustomTypeDescriptor uygulama:

Son zamanlarda, çalışma zamanında eklenebilen ve kaldırılabilen herhangi bir sayıda özelliğe sahip olabilecek bir kayıt nesnesine ızgara görünümü bağlama gereksinimi duydum. Bu, kullanıcının ek bir veri kümesi girmek için bir sonuç kümesine yeni bir sütun eklemesine izin vermekti.

Bu, her verinin 'satırının' anahtar özellik adı ve değerin bir dize veya belirtilen satır için özelliğin değerini depolayabilen bir sınıf olduğu bir sözlük olarak kullanılmasıyla elde edilebilir. Elbette bir Sözlük nesneleri Listesi'ne sahip olmak bir ızgaraya bağlanamayacaktır. ICustomTypeDescriptor burada devreye girer.

Sözlük için bir sarmalayıcı sınıfı yaratarak ve onu ICustomTypeDescriptor arabirimine bağlı kılarak, bir nesnenin özelliklerini döndürme davranışı geçersiz kılınabilir.

Aşağıdaki veri 'satır' sınıfının uygulanmasına bir göz atın:

/// <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

    //---------------------------------------------------------------------------------------------------------------------------
}

Not: GetProperties yönteminde, PropertyDescriptors'ı performans için okuduktan sonra Önbelleğe Alabilirim, ancak çalışma zamanında sütunları ekleyip kaldırırken her zaman yeniden oluşturulmasını istiyorum

Ayrıca GetProperties yönteminde, sözlük girişleri için eklenen Özellik Tanımlayıcılarının TestResultPropertyDescriptor türünde olduğunu da fark edeceksiniz. Bu, özelliklerin nasıl ayarlandığını ve alındığını yöneten özel bir Özellik Tanımlayıcı sınıfıdır. Aşağıdaki uygulamaya bir göz atın:

/// <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

    //---------------------------------------------------------------------------------------------------------------------------
}

Bu sınıfa bakılacak ana özellikler GetValue ve SetValue'dur. Burada bir sözlük olarak çevrilen bileşeni ve bunun içindeki anahtarın değerinin Ayarlandığını veya alındığını görebilirsiniz. Bu sınıftaki sözlüğün Row wrapper sınıfındaki ile aynı türde olması önemlidir, aksi takdirde çevrim başarısız olur. Tanımlayıcı oluşturulduğunda, anahtar (özellik adı) iletilir ve doğru değeri elde etmek için sözlüğü sorgulamak için kullanılır.

Şu adresteki blogumdan alınmıştır:

Dinamik özellikler için ICustomTypeDescriptor Uygulaması


Bunu sonsuza kadar önce yazdığını biliyorum, ama gerçekten cevabına kodunun bir kısmını koymalı ya da yazından bir alıntı yapmalısın. Bence bu kurallarda var - bağlantınız karardığında cevabınız neredeyse anlamsız hale gelir. Ancak, MSDN'de ICustomTypeDescriptor'u arayabildiğiniz için ( msdn.microsoft.com/en-us/library/… )
David Schwartz

@DavidSchwartz - Eklendi.
WraithNath

Seninle tamamen aynı tasarım sorunum var, bu iyi bir çözüm gibi görünüyor. Peki ya bu ya da ben veri bağlamayı ortadan kaldırıyorum ve kullanıcı arayüzünü arkasındaki kod aracılığıyla manuel olarak kontrol ediyorum. Bu yaklaşımla iki yönlü bağlama yapabilir misiniz?
rulo

@rolls evet yapabilirsiniz, sadece özellik tanımlayıcınızın salt okunur olduğunu döndürmediğinden emin olun. Son zamanlarda, verilerin hücrelerde düzenlenmesine izin veren bir ağaç listesindeki verileri gösteren başka bir şey için de benzer bir yaklaşım kullandım
WraithNath

1

WPF tarafından kullanıldığı şekliyle DependencyObjects'e bakmalısınız, bunlar benzer bir modeli izler, böylece özellikler çalışma zamanında atanabilir. Yukarıda belirtildiği gibi, bu sonuçta bir hash tablosu kullanmaya işaret eder.

Göz atmak için bir diğer yararlı şey de CSLA.Net . Kod ücretsiz olarak kullanılabilir ve peşinde olduğunuz görünen bazı ilkeleri \ kalıpları kullanır.

Ayrıca, sıralama ve filtrelemeye bakıyorsanız, bir çeşit ızgara kullanacağınızı tahmin ediyorum. Uygulanacak kullanışlı bir arayüz ICustomTypeDescriptor'dur, bu, nesneniz yansıtıldığında ne olacağını etkili bir şekilde geçersiz kılmanıza olanak tanır, böylece yansıtıcıyı nesnenizin kendi dahili karma tablosuna yönlendirebilirsiniz.


1

Orsogufo'nun bazı kodlarının yerini alması için, yakın zamanda aynı problem için kendim de bir sözlüğe gitmiştim, işte [] operatörüm:

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

Bu uygulama ile ayarlayıcı []=, sözlükte halihazırda mevcut değilse, kullandığınızda yeni anahtar-değer çiftleri ekleyecektir .

Ayrıca, benim propertiesiçin bir IDictionaryve kurucularda onu başlatıyorum new SortedDictionary<string, string>().


Çözümünüzü deniyorum. DTO'm record[name_column] = DBConvert.To<string>(r[name_column]);nerede recordolduğu gibi servis tarafında bir değer ayarlıyorum . İstemci tarafında bu değeri nasıl elde ederim?
RobertKing

1

Sebeplerin ne olduğundan emin değilim ve bunu bir şekilde Reflection Emit ile başarabilsen bile (yapabileceğinden emin değilim), kulağa iyi bir fikir gibi gelmiyor. Muhtemelen daha iyi bir fikir, bir tür Sözlüğe sahip olmaktır ve sınıfınızdaki yöntemlerle sözlüğe erişimi sarmalayabilirsiniz. Bu şekilde, veritabanındaki verileri bu sözlükte depolayabilir ve ardından bu yöntemleri kullanarak geri alabilirsiniz.


0

Neden dizinleyiciye iletilen bir dize değeri olarak özellik adına sahip bir dizin oluşturucu kullanılmıyor?


0

Sınıfınızın bir Dictionary nesnesini ifşa etmesini sağlayamaz mıydınız? "Nesneye daha fazla özellik eklemek" yerine, verilerinizi (bazı tanımlayıcılarla birlikte) çalışma zamanında sözlüğe ekleyebilirsiniz.


0

Bağlama içinse, XAML'den dizin oluşturuculara başvurabilirsiniz.

Text="{Binding [FullName]}"

Burada sınıf indeksleyicisine "FullName" anahtarıyla başvuruyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.