代表组成(功能陷阱)

Mar*_*ner 4 c# delegates functional-programming

当试图在C#中使用代理以功能方式解决问题时,我遇到了一个我想要分享的陷阱.我希望听到你的建议.

背景

我想从一个对象列表中填充一个网格,其中单个列的值是使用委托获得的(想法来自Philip Pipers ObjectListView控件).

另外,我想自动插入包含两个值之间(数值)差异的列.

因此,有特性我的对象FirstValue,SecondValue并且ThirdValue我想和列FirstValue,(SecondValue-FirstValue),SecondValue,(ThirdValue-SecondValue),ThirdValue.

我已经调整了现有的网格控件来在对象列表上使用委托,这部分工作正常.

第一次尝试

首先,我尝试了类似的东西:

class MyGridClass : DelegateGrid
{
  DelegateGrid.ValueGetter lastGetter;

  public MyGridClass() {
    AddMyColumn(delegate(MyObj obj) { return obj.FirstValue; });
    AddMyColumn(delegate(MyObj obj) { return obj.SecondValue; });
    AddMyColumn(delegate(MyObj obj) { return obj.ThirdValue; });
  }

  private void AddMyColumn(DelegateGrid.ValueGetter getter) {
    if (lastGetter != null)
      base.AddColumn(new DelegateColumn(delegate(MyObj obj) { 
        return getter(obj)-lastGetter(obj); 
      }));
    base.AddColumn(new DelegateColumn(getter));
  }
};
Run Code Online (Sandbox Code Playgroud)

问题

在功能性语言,以这种方式计算的差异将很好地工作,因为新的委托(内部构造AddMyColumn)将使用lastGetter施工时间.但在C#中,新的委托使用引用lastGetter,因此执行时,它在执行时使用的实际值.因此,差异将始终针对最后一列(即obj.ThirdValue)构建.

我为自己找到的一个解决方案是

public AddMyColumn(DelegateGrid.ValueGetter getter) {
  if (lastGetter != null) {
    DelegateGrid.ValueGetter newLastGetter = 
      new DelegateGrid.ValueGetter(lastGetter);
    base.AddColumn(new DelegateColumn(delegate(MyObj obj) { 
     return getter(obj)-newLastGetter(obj); 
    }));
  }
  // ...
}
Run Code Online (Sandbox Code Playgroud)

注意

if (lastGetter != null) {
  DelegateGrid.ValueGetter newLastGetter = 
    delegate(MyObject obj){return lastGetter(obj); };
Run Code Online (Sandbox Code Playgroud)

不会解决问题.

已经找到了解决方案,这部分有点形式,但是

  • 有没有人建议更好的解决方案
  • 我正在使用C#2.0并且只对C#3.0中的lambda表达式有一个理论知识:它们是否允许更清晰的解决方案(因此值得他们的名字......)?

Jon*_*eet 7

问题只是捕获变量而不是值.这是一个大致相同但稍微简单的解决方案:

public AddMyColumn(DelegateGrid.ValueGetter getter) {
  if (lastGetter != null) {
    DelegateGrid.ValueGetter newLastGetter = lastGetter;
    base.AddColumn(new DelegateColumn(delegate(MyObj obj) { 
     return getter(obj)-newLastGetter(obj); 
    }));
  }
  // ...
}
Run Code Online (Sandbox Code Playgroud)

基本上没有必要创建一个新的委托实例 - 委托是不可变的,所以你可以只使用赋值复制值.

就捕获的值而言,这实际上并不是特定于委托的问题 - 这通常是匿名方法和lambda表达式的常见问题.典型的例子是;

List<Action> actions = new List<Action>();
for (int i=0; i < 10; i++)
{
    actions.Add(() => Console.WriteLine(i));
}
foreach (Action action in actions)
{
    action();
}
Run Code Online (Sandbox Code Playgroud)

这打印"10"10次.要打印0-9,您需要再次更改捕获变量的范围:

List<Action> actions = new List<Action>();
for (int i=0; i < 10; i++)
{
    int copy = i;
    actions.Add(() => Console.WriteLine(copy));
}
foreach (Action action in actions)
{
    action();
}
Run Code Online (Sandbox Code Playgroud)