【问题标题】:How do I get around this lambda expression outer variable issue?如何解决这个 lambda 表达式外部变量问题?
【发布时间】:2011-02-12 15:03:51
【问题描述】:

我正在使用 PropertyDescriptor 和 ICustomTypeDescriptor (still) 尝试将 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",这是 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);
    }
}

【问题讨论】:

标签: c# lambda wpfdatagrid icustomtypedescriptor


【解决方案1】:

简单地说:

        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,在技术上是在循环外部声明的)。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      Marc 的解决方案当然是正确的,但我想我会在下面详细说明 WHY。我们大多数人都知道,如果您在 forforeach 语句中声明一个变量,它只会与里面的内容一样长,这使得它看起来就像变量一样在这种语句的语句块中声明的变量,但这是不对的。

      为了更好地理解它,请使用以下 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 的解决方案显示了放置变量的位置,以便每次都会捕获唯一的。

      【讨论】:

        【解决方案4】:

        【讨论】:

        • 非常感谢那些,foreach扩展中的m = (int)(int)e.Current等双重转换的原因是什么?
        • @panamack:我是个书呆子。规范指出,电流首先转换为枚举器的类型,然后转换为循环变量的类型。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-17
        • 1970-01-01
        • 2012-06-18
        • 1970-01-01
        相关资源
        最近更新 更多