我如何解决这个lambda表达式外部变量问题?

时间:2010-04-26 22:28:40

标签: c# lambda wpfdatagrid icustomtypedescriptor

我正在使用PropertyDescriptor和ICustomTypeDescriptor(still)尝试将WPF DataGrid绑定到一个对象,数据存储在一个Dictionary中。

因为如果你向WPF DataGrid传递一个Dictionary对象列表,它将根据字典的公共属性(Comparer,Count,Keys和Values)自动生成列我的Person子类Dictionary并实现ICustomTypeDescriptor。

ICustomTypeDescriptor定义一个返回PropertyDescriptorCollection的GetProperties方法。

PropertyDescriptor是抽象的,所以你必须将它子类化,我想我有一个构造函数,它接受Func和一个Action参数,它们委托字典中值的获取和设置。

然后我为字典中的每个Key创建一个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”,“年龄“,”性别“他们都得到并设置”性别“,这是在foreach循环中 s 的最终静止值。

如何确保每个代理使用所需的字典密钥,即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 个答案:

答案 0 :(得分: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,在技术上声明循环之外)。

答案 1 :(得分:3)

Marc的解决方案当然是正确的,但我想我会扩展为什么在下面。正如我们大多数人所知,如果你在forforeach语句中声明一个变量,它只能存在于内部的内容,这使得似乎就像变量一样与在这样一个声明的声明块中声明的变量相同,但这不对。

要更好地理解它,请采取以下for循环。然后我将以while-form重新陈述“等效”循环。

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”值,以及为什么Marc的解决方案显示了将变量放在何处以便每次都会捕获一个独特的。

答案 2 :(得分:2)

s循环中创建for的本地副本并使用它。

for(string s in this.Keys) {
string key = s;
//...
}

答案 3 :(得分:2)