このLambda式の外部変数の問題を回避するにはどうすればよいですか?
-
01-10-2019 - |
質問
PropertyDescriptorおよびicustomtypedescriptorで遊んでいます(まだ)WPF Datagridをオブジェクトにバインドしようとします。オブジェクトには、データが辞書に保存されます。
WPF Datagridを辞書オブジェクトのリストに渡すと、辞書(比較、カウント、キー、値)のパブリックプロパティに基づいて列を自動生成します。
icustomtypedescriptorは、PropertyDescriptorCollectionを返すGetPropertiesメソッドを定義します。
PropertyDescriptorは抽象的であるため、Subclassを使用する必要があります。FUNCを使用したコンストラクターと、辞書の値の取得と設定を委任するアクションパラメーターがあると思いました。
次に、次のような辞書の各キーの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);
}
問題は、各プロパティが独自のファンとアクションを取得するが、すべてが外側の変数を共有することです s したがって、Datagridは「ID」、「FirstName」、「lastName」、「age」、「jender」の列を自動誘発しますが、それらはすべて「性別」に反して設定します。 s foreachループで。
各デリゲートが目的の辞書キー、つまりFUNC/アクションがインスタンス化された時点での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);
}
}
解決
マークの解決策はもちろん正しいですが、以下の理由を拡大すると思いました。私たちのほとんどが知っているように、あなたが変数を宣言した場合 for
また foreach
声明、それは中にある限りのみ生きているので、それはそれを作ります 思われる 変数のように、そのようなステートメントのステートメントブロックで宣言された変数と同じですが、それは正しくありません。
それをよりよく理解するには、次のループを取ります。次に、「同等の」ループを長時間形成して再定格します。
for(int i = 0; i < list.Length; i++)
{
string val;
list[i] = list[i]++;
val = list[i].ToString();
Console.WriteLine(val);
}
これは、以下のようにwhile-formで機能します:(それはまったく同じではありません。 continue
異なって行動しますが、スコーピングルールの場合、それは同じです)
{
int i = 0;
while(i < list.Length)
{
{
string val;
list[i] = list[i]++;
val = list[i].ToString();
Console.WriteLine(val);
}
i++;
}
}
このように「爆発」したとき、変数の範囲がより明確になり、なぜそれがあなたのプログラムで同じ「s」値をキャプチャする理由を見ることができます、そしてマークのソリューションがあなたの変数をどこに配置するかを示す理由を見ることができます毎回捕らえられました。
他のヒント
単に:
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
, 、技術的に宣言されています 外側 ループ)。
のローカルコピーを作成します s
あなたの内側 for
ループしてそれを使用します。
for(string s in this.Keys) {
string key = s;
//...
}
この問題に関するいくつかの追加の考えについては、参照してください
http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/