关于foreach和委托的问题

5
假设以下代码:
foreach(Item i on ItemCollection)
{
   Something s = new Something();
   s.EventX += delegate { ProcessItem(i); };
   SomethingCollection.Add(s);
}

当然,这是错误的,因为所有委托都指向同一个项目。 可以选择以下替代方案:
foreach(Item i on ItemCollection)
{
   Item tmpItem = i;
   Something s = new Something();
   s.EventX += delegate { ProcessItem(tmpItem); };
   SomethingCollection.Add(s);
}

在这种情况下,所有的委托都指向它们自己的项目。

这种方法怎么样?还有其他更好的解决方案吗?


你能否发布完整的代码,以便编译并显示差异? - empi
你可以使用C# 1.0反编译第一段代码,然后就能看到区别了。 - Vitaliy Liptchinsky
5个回答

11

更新:这个问题已经有详细的分析和评论,在这里:

http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/


这是一个非常常见的问题;通常会被报告为编译器bug,但实际上,根据规范,编译器做得很正确。匿名函数闭包捕获的是变量而不是,且只有一个foreach循环变量。因此每个lambda都捕获同一个变量,从而得到该变量的当前值。

这对几乎所有人来说都是令人惊讶的,导致了很多混淆和很多bug报告。我们在考虑改变C#的某个未来版本的规范和实现,使循环变量在逻辑上被声明在循环结构内,每次循环时提供一个“新”变量。

这将是一项破坏性的变更,但我怀疑依赖于这种奇怪行为的人数相当少。如果您对此有意见,请随时在上面提到的博客文章中添加评论。谢谢!


1
而且,显示编译器警告而不是更改行为不是更好吗? - FerranB
1
实际上,在这种情况下,编译器警告可能是必要的。不清楚哪个更好。我们将考虑两者。 - Eric Lippert

5
第二段代码是在所有其他条件保持不变的情况下,可以得到的最佳方法。
但是,在Something上创建一个接受Item的属性可能是可行的。然后,事件代码可以从事件的发送者中访问这个Item,或者它可能包含在事件参数中。因此消除了闭包的需要。
个人认为,“消除不必要的闭包”是一种值得进行的重构,因为对它们进行推理可能很困难。

是的,这正是我所想的,但表达得更加优美 :) - Hath

1
如果ItemCollection是一个(通用)列表,您可以使用它的ForEach方法。它将为每个i提供一个新的作用域:
ItemCollection.ForEach(
    i =>
    {
        Something s = new Something();
        s.EventX += delegate { ProcessItem(i); };
        SomethingCollection.Add(s);
    });

或者您也可以使用其他适合的Linq-方法 - 例如Select

var somethings = ItemCollection.Select(
        i =>
        {
            Something s = new Something();
            s.EventX += delegate { ProcessItem(i); };
            return s;
        });
foreach(Something s in somethings)
    SomethingCollection.Add(s);

0
foreach(Item i on ItemCollection)
{
   Something s = new Something(i);
   s.EventX += (sender, eventArgs) => { ProcessItem(eventArgs.Item);};
   SomethingCollection.Add(s);
}

你可以直接将 'i' 传入你的 'Something' 类中,并在 EventX 的事件参数中使用它。


0
你面临的问题与闭包这种语言结构有关。第二段代码可以解决这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接