如何解决 lambda 表达式外部变量问题?
-
01-10-2019 - |
题
我正在使用 PropertyDescriptor 和 ICustomTypeDescriptor (仍然)尝试将 WPF DataGrid 绑定到一个对象,该对象的数据存储在字典中。
因为如果您向 WPF DataGrid 传递一个 Dictionary 对象列表,它将根据字典的公共属性(比较器、计数、键和值)自动生成列,我的 Person 子类化了 Dictionary 并实现了 ICustomTypeDescriptor。
ICustomTypeDescriptor 定义了一个 GetProperties 方法,该方法返回 PropertyDescriptorCollection。
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”列,但它们都根据“Gender”获取和设置,这是最终的静态值 s 在 foreach 循环中。
如何确保每个代表使用所需的字典键,即实例化 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);
}
}
解决方案
Marc的解决方案当然是正确的,但我认为我会扩展下面的原因。正如我们大多数人所知道的那样,如果您声明一个变量 for
或者 foreach
陈述,它只有内部的时间,这使它成为 似乎 像变量一样,与此类语句的语句障碍中声明的变量相同,但这是不正确的。
要更好地理解它,请采取以下循环。然后,我将以一定的形式重新调整“等效”循环。
for(int i = 0; i < list.Length; i++)
{
string val;
list[i] = list[i]++;
val = list[i].ToString();
Console.WriteLine(val);
}
这是按照以下形式进行的:(它并不完全相同,因为 continue
会采取不同的行动,但对于范围的规则,是一样的)
{
int i = 0;
while(i < list.Length)
{
{
string val;
list[i] = list[i]++;
val = list[i].ToString();
Console.WriteLine(val);
}
i++;
}
}
当以这种方式“爆炸”时,变量的范围变得更加清晰,您可以看到为什么它总是在程序中捕获相同的“ s”值,以及为什么MARC的解决方案显示在哪里放置您的变量,以使一个唯一的一个是每次都被捕获。
其他提示
简单地:
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;
//...
}