是否有理由使用C#';如何在foreach中重用变量?

是否有理由使用C#';如何在foreach中重用变量?,c#,foreach,lambda,scope,anonymous-methods,C#,Foreach,Lambda,Scope,Anonymous Methods,在C#中使用lambda表达式或匿名方法时,我们必须小心访问修改的闭包陷阱。例如: foreach (var s in strings) { query = query.Where(i => i.Prop == s); // access to modified closure ... } foreach (var s in strings) query = query.Where(i => i.Prop == s); // access to modified

在C#中使用lambda表达式或匿名方法时,我们必须小心访问修改的闭包陷阱。例如:

foreach (var s in strings)
{
   query = query.Where(i => i.Prop == s); // access to modified closure
   ...
}
foreach (var s in strings)
    query = query.Where(i => i.Prop == s); // access to modified closure
int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();
由于修改了闭包,上述代码将导致查询中的所有
Where
子句基于
s
的最终值

如上所述,之所以会发生这种情况,是因为在上面的
foreach
循环中声明的
s
变量在编译器中的转换方式如下:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
而不是像这样:

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}
如前所述,在循环外部声明变量没有性能优势,在正常情况下,我能想到的唯一原因是如果您计划在循环范围之外使用变量:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
但是,
foreach
循环中定义的变量不能在循环外使用:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
因此,编译器声明变量的方式使其极易发生错误,而这些错误通常很难找到和调试,同时不会产生任何明显的好处


如果使用内部作用域变量编译
foreach
循环,您是否可以以这种方式处理它们,或者这只是在匿名方法和lambda表达式可用或常见之前所做的任意选择,从那以后,哪一个没有被修改过?

埃里克·利珀特在他的博客文章及其续集中彻底地阐述了你的问题

对我来说,最有说服力的论点是,在每次迭代中使用新变量将与(;;)样式循环不一致。对于(int i=0;i<10;i++),您希望在每次迭代的
中都有一个新的
int i

这种行为最常见的问题是对迭代变量进行闭包,它有一个简单的解决方法:

foreach (var s in strings)
{
    var s_for_closure = s;
    query = query.Where(i => i.Prop == s_for_closure); // access to modified closure

我关于这个问题的博文:。

我已经被这个问题所困扰,我有一个习惯,就是在我用来传递给任何闭包的最内层范围中包含本地定义的变量。在您的示例中:

foreach (var s in strings)
{
   query = query.Where(i => i.Prop == s); // access to modified closure
   ...
}
foreach (var s in strings)
    query = query.Where(i => i.Prop == s); // access to modified closure
int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();
我有:

一旦你有了这个习惯,你就可以避免在非常罕见的情况下绑定到外部作用域。老实说,我想我从来没有这样做过

编译器声明变量的方式使其极易发生错误,而这些错误通常很难找到和调试,同时不会产生任何明显的好处

你的批评是完全有道理的

我在这里详细讨论这个问题:

如果foreach循环是使用内部作用域变量编译的,那么您是否可以用这种方式处理它们?或者这只是一个在匿名方法和lambda表达式可用或普遍使用之前做出的任意选择,并且从那时起就没有被修改过

后者。C#1.0规范实际上没有说明循环变量是在循环体内部还是外部,因为它没有明显的区别。在C#2.0中引入闭包语义时,选择将循环变量置于循环之外,与“for”循环一致

我认为公平地说,所有人都对这一决定感到遗憾。这是C#中最糟糕的“陷阱”之一,我们将采用突破性的更改来修复它。在C#5中,foreach循环变量将在逻辑上位于循环体内部,因此每次闭包都将获得一个新的副本

不会更改
循环的
,也不会将更改“后端口”到以前版本的C#。因此,在使用这个习惯用法时,您应该继续小心。

在C#5.0中,这个问题是固定的,您可以关闭循环变量并获得预期的结果

语言规范规定:

8.8.4 foreach声明 (……)

形式的foreach语句

foreach (V v in x) embedded-statement
然后扩展到:

{
  E e = ((C)(x)).GetEnumerator();
  try {
      while (e.MoveNext()) {
          V v = (V)(T)e.Current;
          embedded-statement
      }
  }
  finally {
      … // Dispose e
  }
}
(……)

v
在while循环中的位置对于它的工作方式很重要 由中发生的任何匿名函数捕获 嵌入语句。例如:

foreach (var s in strings)
{
   query = query.Where(i => i.Prop == s); // access to modified closure
   ...
}
foreach (var s in strings)
    query = query.Where(i => i.Prop == s); // access to modified closure
int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();
如果
v
是在while循环之外声明的,那么它将被共享 在所有迭代中,它在for循环之后的值将是 最终值,
13
,这是调用
f
将打印的内容。 相反,因为每个迭代都有自己的变量
v
,所以 在第一次迭代中由
f
捕获的值将继续保留该值
7
,这是将要打印的内容。(注意:C的早期版本# 在while循环之外声明
v


字符串s有什么问题;foreach(s in strings){…}
?@BradChristie OP实际上并不是在谈论
foreach
,而是在谈论导致类似代码的lamda表达式,如OP所示。@BradChristie:编译吗?(错误:foreach语句中对我来说类型和标识符都是必需的)@JakobBotschNielsen:这是lambda的一个封闭的外部局部;为什么你认为它会在堆栈上呢?它的生命周期比堆栈帧长@埃里克利珀特:我很困惑。我知道lambda捕获了对foreach变量的引用(该变量在循环外部内部声明),因此最终会与它的最终值进行比较;这是我得到的。我不明白的是,在循环中声明变量会有什么不同。从编译器编写器的角度来看,我只在堆栈上分配一个字符串引用(var's'),而不管声明是在循环内部还是外部;我当然不想每次迭代都将新引用推到堆栈上!归根结底,当人们写这篇文章时,他们真正想要的不是有多个变量,而是结束这个值。在一般情况下,很难想出一个可用的语法,这就是典型的wor