C# 是否有理由在 foreach 中重用变量? [英] Is there a reason for C#'s reuse of the variable in a foreach?

查看:12

问题描述

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

When using lambda expressions or anonymous methods in C#, we have to be wary of the access to modified closure pitfall. For example:

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

由于修改了闭包,上面的代码会导致查询的所有Where子句都基于s的最终值.

Due to the modified closure, the above code will cause all of the Where clauses on the query to be based on the final value of s.

此处所述,这发生的原因是在上面的 foreach 循环中声明的 s 变量在编译器中是这样翻译的:

As explained here, this happens because the s variable declared in foreach loop above is translated like this in the compiler:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}

而不是这样:

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}

正如此处指出的那样,在循环外声明变量没有性能优势,并且在正常情况下我能想到这样做的唯一原因是,如果您打算在循环范围之外使用该变量:

As pointed out here, there are no performance advantages to declaring a variable outside the loop, and under normal circumstances the only reason I can think of for doing this is if you plan to use the variable outside the scope of the loop:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;

但是在 foreach 循环中定义的变量不能在循环外使用:

However variables defined in a foreach loop cannot be used outside the loop:

foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.

因此,编译器以某种方式声明变量,使其极易出现通常难以查找和调试的错误,同时不会产生任何明显的好处.

So the compiler declares the variable in a way that makes it highly prone to an error that is often difficult to find and debug, while producing no perceivable benefits.

您是否可以使用 foreach 循环以这种方式执行某些操作,而如果它们是使用内部作用域变量编译的,则您不能这样做,或者这只是在匿名方法之前做出的任意选择和 lambda 表达式是可用的或常见的,从那时起就没有修改过?

Is there something you can do with foreach loops this way that you couldn't if they were compiled with an inner-scoped variable, or is this just an arbitrary choice that was made before anonymous methods and lambda expressions were available or common, and which hasn't been revised since then?

推荐答案

编译器声明变量的方式使其极易出现通常难以查找和调试的错误,同时不会产生明显的好处.

The compiler declares the variable in a way that makes it highly prone to an error that is often difficult to find and debug, while producing no perceivable benefits.

你的批评完全有道理.

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

I discuss this problem in detail here:

结束循环变量被认为有害

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

Is there something you can do with foreach loops this way that you couldn't if they were compiled with an inner-scoped variable? or is this just an arbitrary choice that was made before anonymous methods and lambda expressions were available or common, and which hasn't been revised since then?

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

The latter. The C# 1.0 specification actually did not say whether the loop variable was inside or outside the loop body, as it made no observable difference. When closure semantics were introduced in C# 2.0, the choice was made to put the loop variable outside the loop, consistent with the "for" loop.

我认为可以公平地说,所有人都对那个决定感到遗憾.这是 C# 中最糟糕的问题"之一,我们将采取重大更改来修复它.在 C# 5 中,foreach 循环变量将在逻辑上在内部循环体,因此闭包每次都会得到一个新的副本.

I think it is fair to say that all regret that decision. This is one of the worst "gotchas" in C#, and we are going to take the breaking change to fix it. In C# 5 the foreach loop variable will be logically inside the body of the loop, and therefore closures will get a fresh copy every time.

for 循环将不会改变,并且不会反向移植"到以前版本的 C#.因此,您在使用此习语时应继续小心.

The for loop will not be changed, and the change will not be "back ported" to previous versions of C#. You should therefore continue to be careful when using this idiom.

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆