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)
不会解决问题.
题
已经找到了解决方案,这部分有点形式,但是
问题只是捕获变量而不是值.这是一个大致相同但稍微简单的解决方案:
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)