代表组成(功能陷阱)

时间:2009-03-30 10:46:46

标签: c# functional-programming delegates

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

背景

我想从一个对象列表中填充一个网格,其中使用委托来获取单个列的值(从Philip Pipers ObjectListView control借来的想法)。

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

所以我的对象具有属性FirstValueSecondValueThirdValue我希望列有FirstValue(SecondValue-FirstValue)SecondValue,{ {1}},(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)); } }; 内构建)将使用AddMyColumn施工时间。但是在C#中,新委托使用引用lastGetter,因此在执行时,它使用执行时的实际值。因此,差异将始终针对最后一列(即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); 
    }));
  }
  // ...
}

不会解决问题。

问题

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

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

2 个答案:

答案 0 :(得分: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); 
    }));
  }
  // ...
}

基本上没有必要创建一个新的委托实例 - 委托是不可变的,所以你只需要通过赋值复制该值。

就捕获的值而言,这不是一个特定于委托的问题 - 这通常是匿名方法和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();
}

这打印“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();
}

答案 1 :(得分:0)

为了回答你的其他观点,lambda语法会使它变得更好,因为它们会减少冗长的代码。

delegate(MyObj obj) { 
    return getter(obj)-newLastGetter(obj); 
}

变为:

obj => getter(obj)-newLastGetter(obj)