4

PropertyDescriptor と ICustomTypeDescriptor (まだ) で遊んでいて、データが辞書に格納されているオブジェクトに WPF DataGrid をバインドしようとしています。

WPF DataGrid に Dictionary オブジェクトのリストを渡すと、辞書のパブリック プロパティ (Comparer、Count、Keys、および Values) に基づいて列が自動生成されるため、私の Person は Dictionary をサブクラス化し、ICustomTypeDescriptor を実装します。

ICustomTypeDescriptor は、PropertyDescriptorCollection を返す GetProperties メソッドを定義します。

PropertyDescriptor は抽象的であるため、サブクラス化する必要があります。Func を受け取るコンストラクターと、ディクショナリ内の値の取得と設定を委任する Action パラメーターが必要だと考えました。

次に、次のようにディクショナリ内の各キーに対して PersonPropertyDescriptor を作成します。

            foreach (string s in this.Keys)
            {
                var descriptor = new PersonPropertyDescriptor(
                        s,
                        new Func<object>(() => { return this[s]; }),
                        new Action<object>(o => { this[s] = o; }));
                propList.Add(descriptor);
            }

問題は、各プロパティが独自の Func と Action を取得することですが、それらはすべて外部変数sを共有するため、DataGrid は「ID」、「FirstName」、「LastName」、「Age」、「Gender」の列を自動生成しますが、それらはすべて取得され、foreach ループ内の s の最終値である「Gender」に対して設定します。

各デリゲートが目的のディクショナリ キー、つまり Func/Action がインスタンス化された時点での s の値を確実に使用するようにするにはどうすればよいですか?

とても感謝しております。


これが私のアイデアの残りの部分です。ここで実験しているだけです。これらは「実際の」クラスではありません...

// DataGrid binds to a People instance
public class People : List<Person>
{
    public People()
    {
        this.Add(new Person());
    }
}

public class Person : Dictionary<string, object>, ICustomTypeDescriptor
{
    private static PropertyDescriptorCollection descriptors;

    public Person()
    {
        this["ID"] = "201203";
        this["FirstName"] = "Bud";
        this["LastName"] = "Tree";
        this["Age"] = 99;
        this["Gender"] = "M";        
    }        

    //... other ICustomTypeDescriptor members...

    public PropertyDescriptorCollection GetProperties()
    {
        if (descriptors == null)
        {
            var propList = new List<PropertyDescriptor>();

            foreach (string s in this.Keys)
            {
                var descriptor = new PersonPropertyDescriptor(
                        s,
                        new Func<object>(() => { return this[s]; }),
                        new Action<object>(o => { this[s] = o; }));
                propList.Add(descriptor);
            }

            descriptors = new PropertyDescriptorCollection(propList.ToArray());
        }

        return descriptors;
    }

    //... other other ICustomTypeDescriptor members...

}

public class PersonPropertyDescriptor : PropertyDescriptor
{
    private Func<object> getFunc;
    private Action<object> setAction;

    public PersonPropertyDescriptor(string name, Func<object> getFunc, Action<object> setAction)
        : base(name, null)
    {
        this.getFunc = getFunc;
        this.setAction = setAction;
    }

    // other ... PropertyDescriptor members...

    public override object GetValue(object component)
    {
        return getFunc();
    }

    public override void SetValue(object component, object value)
    {
        setAction(value);
    }
}
4

4 に答える 4

7

単に:

        foreach (string s in this.Keys)
        {
            string copy = s;
            var descriptor = new PersonPropertyDescriptor(
                    copy,
                    new Func<object>(() => { return this[copy]; }),
                    new Action<object>(o => { this[copy] = o; }));
            propList.Add(descriptor);
        }

キャプチャされた変数では、重要なのは宣言されている場所です。したがって、キャプチャされた変数をループ内で宣言することにより、反復ごとに異なるキャプチャ クラスのインスタンスを取得します (ループ変数 は、技術的にはループのsで宣言されます)。

于 2010-04-26T22:30:51.937 に答える
3

Marc の解決策はもちろん正しいですが、以下の WHY について詳しく説明したいと思います。私たちのほとんどが知っているように、fororforeachステートメントで変数を宣言すると、変数はその中にあるものだけ存続し、その変数はそのようなステートメントのステートメント ブロックで宣言された変数と同じように見えますが、それは正しくありません。

理解を深めるために、次の for ループを見てみましょう。次に、「同等の」ループを while 形式で再度述べます。

for(int i = 0; i < list.Length; i++)
{
    string val;
    list[i] = list[i]++;
    val = list[i].ToString();
    Console.WriteLine(val);
}

これは、以下のように while 形式で機能します: (動作が異なるため、まったく同じではありませんcontinueが、スコープ ルールについては同じです)

{
    int i = 0;
    while(i < list.Length)
    {
        {
            string val;
            list[i] = list[i]++;
            val = list[i].ToString();
            Console.WriteLine(val);
        }
        i++;
    }
}

このように「分解」すると、変数の範囲がより明確になり、プログラムで常に同じ「s」値をキャプチャする理由と、Marc のソリューションが変数をどこに配置して一意の変数を配置するかを示す理由がわかります。毎回捕獲。

于 2010-04-26T22:45:04.490 に答える
2

この問題に関するその他の考えについては、を参照してください。

http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/

于 2010-04-26T23:29:44.020 に答える
2

ループs内のローカル コピーを作成し、それを使用します。for

for(string s in this.Keys) {
string key = s;
//...
}
于 2010-04-26T22:33:41.073 に答える