C#で動的プロパティを作成するにはどうすればよいですか?


88

静的プロパティのセットを持つクラスを作成する方法を探しています。実行時に、データベースからこのオブジェクトに他の動的プロパティを追加できるようにしたいと思います。また、これらのオブジェクトにソート機能とフィルタリング機能を追加したいと思います。

C#でこれを行うにはどうすればよいですか?


3
このクラスの目的は何ですか?あなたのリクエストは私が本当にデザインパターンか何かが必要だと疑っていますが、あなたのユースケースが何であるかわからないということは私が実際に提案をしていないことを意味します。
Brian

回答:


60

あなたは辞書を使うかもしれません、例えば

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

    }
}

30

あなたはデータバインディングの目的のためにこれを必要とする場合は、実装することで...カスタム記述モデルでこれを行うことができICustomTypeDescriptorTypeDescriptionProviderおよび/またはTypeCoverter、あなたはあなた自身の作成することができPropertyDescriptor、実行時にインスタンスを。これはDataGridViewPropertyGridなどのコントロールがプロパティを表示するために使用するものです。

リストにバインドするには、必要があるだろうITypedListIList。基本的な並べ替え:IBindingList; フィルタリングや高度なソートのために:IBindingListView。「新しい行」を完全にサポートするには(DataGridView):(ICancelAddNewphew!)。

でもそれは大変な仕事です。DataTable(私はそれが嫌いですが)同じことをする安価な方法です。データバインディングが必要ない場合は、ハッシュテーブルを使用してください;-p

ここに簡単な例があります-しかし、あなたはもっとたくさんのことができます...


おかげで...直接データバインドできることが私が探していたものです。したがって、基本的には、オブジェクトコレクションをDataTableに変換してから、代わりにテーブルをバインドするのが最も簡単な方法です。変換後も気になることがもっとあると思います。入力ありがとうございます。
Eatdoku、2009年

サイドノートとして、ICustomTypeDescriptorを介して結合データは、Silverlightでサポートされていない:(。
カートHagenlocher

サイドノートのサイドノードとして、Silverlight 5はICustomTypeDescriptorの代わりにICustomTypeProviderインターフェイスを導入しました。ICustomTypeProviderはその後、.NET Framework 4.5に移植され、Silverlightと.NET Framework間の移植性を可能にしました。:)。
Edward



12

あなたが本当にやりたいことを本当にやりたいと思っているのかはわかりませんが、理由は私にはわかりません。

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)の通常のプロパティのように使用することもできます。


4

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実装


私はあなたがこれを永遠に書いたことを知っていますが、あなたは本当にあなたの答えにあなたのコードのいくつかを入れるか、あなたの投稿から何かを引用すべきです。それはルールにあると思います-リンクが暗くなると、あなたの答えはほとんど無意味になります。ただし、MSDN(msdn.microsoft.com/en-us/library/…)でICustomTypeDescriptorを検索できるため、反対票を投じない
David Schwartz

@DavidSchwartz-追加されました。
WraithNath 2016

私はあなたとまったく同じデザインの問題を抱えています。これは良い解決策のようです。まあ、これか私はデータバインディングを廃止して、私の見解では背後のコードを介して手動でUIを制御します。このアプローチで双方向バインディングを実行できますか?
ロール

@rollsはい、できます。プロパティ記述子がその読み取り専用を返さないことを確認してください。私は最近、セル内でデータを編集できるツリーリストにデータを表示する他の何かにも同様のアプローチを使用しました
WraithNath

1

WPFで使用されるDependencyObjectsを調べる必要があります。これらは、実行時にプロパティを割り当てることができる同様のパターンに従います。上記のように、これは最終的にハッシュテーブルの使用を指します。

もう1つ、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>()ます。


私はあなたの解決策を試みています。私はDTO record[name_column] = DBConvert.To<string>(r[name_column]);がどこであるかサービス側で値を設定していますrecord。クライアント側でこの値を取得するにはどうすればよいですか?
Rohaan 2016年

1

理由がわからないので、Reflection Emitを使ってなんとかして引き出すことができたとしても(できるとは思いませんが)、いい考えではないようです。おそらくより良いアイデアは、なんらかのディクショナリを用意し、クラスのメソッドを介してディクショナリへのアクセスをラップすることです。これにより、データベースのデータをこのディクショナリに格納し、それらのメソッドを使用してデータを取得できます。


0

プロパティ名を持つインデクサーを、インデクサーに渡される文字列値として使用しないのはなぜですか?


0

クラスにDictionaryオブジェクトを公開させることはできませんか?「オブジェクトにプロパティを追加する」代わりに、実行時にデータ(識別子を含む)を辞書に挿入するだけで済みます。


0

バインディング用の場合は、XAMLからインデクサーを参照できます

Text="{Binding [FullName]}"

ここでは、キー "FullName"でクラスインデクサーを参照しています

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.