Str*_*ior 1631 c# foreach lambda scope anonymous-methods
在C#中使用lambda表达式或匿名方法时,我们必须警惕对修改后的闭包陷阱的访问.例如:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
Run Code Online (Sandbox Code Playgroud)
由于修改后的闭包,上面的代码将导致Where
查询中的所有子句都基于最终值s
.
正如这里所解释的那样,这是因为上面循环中s
声明的变量foreach
在编译器中被翻译成这样:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
Run Code Online (Sandbox Code Playgroud)
而不是像这样:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
Run Code Online (Sandbox Code Playgroud)
正如这里所指出的,在循环外声明变量没有性能优势,在正常情况下,我能想到这样做的唯一原因是你计划在循环范围之外使用变量:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
Run Code Online (Sandbox Code Playgroud)
但是,foreach
循环中定义的变量不能在循环外使用:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
Run Code Online (Sandbox Code Playgroud)
因此,编译器以某种方式声明变量,使其非常容易出现通常难以查找和调试的错误,同时不会产生可感知的好处.
你是否可以通过foreach
这种方式处理循环,如果它们是使用内部范围的变量编译的话,你可以做到这一点,或者这只是在匿名方法和lambda表达式可用或普通之前做出的任意选择,并且没有从那时起被修改过?
Eri*_*ert 1378
编译器以一种方式声明变量,使其非常容易出现通常难以查找和调试的错误,同时不会产生可感知的好处.
你的批评是完全合理的.
我在这里详细讨论这个问题:
有没有东西你可以用foreach循环这样做,如果它们是用内部范围的变量编译你不能?或者这只是在匿名方法和lambda表达式可用或常见之前做出的任意选择,并且从那以后还没有修改过?
后者.C#1.0规范实际上没有说明循环变量是在循环体内部还是外部,因为它没有产生可观察到的差异.当在C#2.0中引入闭包语义时,选择将循环变量放在循环之外,与"for"循环一致.
我认为所有人都对这一决定感到遗憾是公平的.这是C#中最糟糕的"陷阱"之一,我们将采取突破性的改变来解决它.在C#5中,foreach循环变量将在逻辑上位于循环体内,因此闭包每次都会得到一个新的副本.
该for
循环将不会改变,并且改变将不会被"向后移植"到C#的早期版本.因此,在使用这个习语时你应该继续小心.
Kri*_*izz 184
Eric Lippert在他的博客文章中完全涵盖了你所要求的内容.关闭循环变量被认为是有害的及其续集.
对我来说,最有说服力的论点是在每次迭代中使用新变量将与for(;;)
样式循环不一致.你期望int i
在每次迭代中有一个新的for (int i = 0; i < 10; i++)
吗?
这种行为最常见的问题是对迭代变量进行闭包,它有一个简单的解决方法:
foreach (var s in strings)
{
var s_for_closure = s;
query = query.Where(i => i.Prop == s_for_closure); // access to modified closure
Run Code Online (Sandbox Code Playgroud)
我的博客文章关于这个问题:关闭C#中的foreach变量.
God*_*eke 101
受到这种困扰,我习惯在最里面的范围中包含本地定义的变量,我用它来转移到任何闭包.在你的例子中:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
Run Code Online (Sandbox Code Playgroud)
我做:
foreach (var s in strings)
{
string search = s;
query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.
Run Code Online (Sandbox Code Playgroud)
一旦你有这种习惯,你就可以在非常罕见的情况下避免它,你实际上打算绑定到外部范围.说实话,我不认为我曾经这样做过.
Pao*_*tti 59
在C#5.0中,此问题已修复,您可以关闭循环变量并获得预期的结果.
语言规范说:
8.8.4 foreach声明
(......)
表格的foreach声明
Run Code Online (Sandbox Code Playgroud)foreach (V v in x) embedded-statement
然后扩展到:
Run Code Online (Sandbox Code Playgroud){ E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } }
(......)
v
while循环内部的放置对于嵌入式语句中发生的任何匿名函数如何捕获它非常重要.例如:Run Code Online (Sandbox Code Playgroud)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#v
在while循环之外声明.)
归档时间: |
|
查看次数: |
99926 次 |
最近记录: |