关于foreach和代表的问题

假设以下代码:

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

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

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

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

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


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

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

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

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

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

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

如果ItemCollection是(通用)List ,则可以使用其ForEach -method。 每个我会给你一个新的范围:

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

或者您可以使用任何其他合适的Linq方法 – 例如选择 :

 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中使用它