假设以下代码:

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);
}

在这种情况下,所有代表都指向他们自己的项目。

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

有帮助吗?

解决方案

更新:这里有关于这个问题的广泛分析和评论:

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


这是一个非常频繁报道的问题;通常它被报告为编译器错误,但实际上编译器根据规范做了正确的事情。匿名函数关闭变量,而非,并且只有一个foreach循环变量。因此,每个lambda都关闭同一个变量,因此得到该变量的当前值。

这对几乎所有人来说都是令人惊讶的,并导致很多混乱和许多错误报告。我们考虑更改C#的假设未来版本的规范和实现,以便循环变量在循环结构内被逻辑声明,给出“新鲜”。每次循环变量。

这将是一个突破性的变化,但我怀疑依赖这种奇怪行为的人数很少。如果您对此主题有任何意见,请随时在上述更新中提及的博客文章中添加评论。谢谢!

其他提示

第二块代码只是最好的方法,你可以让所有其他东西保持不变。

但是,可以在 Something 上创建一个属性,它接受 Item 。反过来,事件代码可以从事件的发送者访问此 Item ,或者它可能包含在事件的eventargs中。因此无需关闭。

就个人而言,我已经添加了“消除不必要的关闭”。作为一种有价值的重构,因为很难对它们进行推理。

如果ItemCollection是(通用)列表,您可以使用其 ForEach - 方法。它会给你一个新的范围:

ItemCollection.ForEach(
    i =>
    {
        Something s = new Something();
        s.EventX += delegate { ProcessItem(i); };
        SomethingCollection.Add(s);
    });

或者您可以使用任何其他合适的 Linq -method - 如选择

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

您在此处遇到的问题与闭包等语言构造有关。 第二段代码解决了这个问题。

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

你不只是将'i'传递到你的'Something'类并在EventX的事件args中使用它

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top