Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/292.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 迭代器的Lambda捕获问题?_C#_Lambda_Scope_Closures - Fatal编程技术网

C# 迭代器的Lambda捕获问题?

C# 迭代器的Lambda捕获问题?,c#,lambda,scope,closures,C#,Lambda,Scope,Closures,如果已经问过这个问题,我表示歉意,但是假设我们有这个代码(我用Mono 2.10.2运行它,并用gmcs2.10.2.0编译): 使用系统; 公共类应用程序{ 公共静态void Main(字符串[]args){ Func f=null; var strs=新字符串[]{ “福”, “酒吧”, “南非兰特” }; foreach(strs中的var str){ 如果(“foo”。等于(str)) f=()=>str; } Console.WriteLine(f());//[1]:打印“zar” f

如果已经问过这个问题,我表示歉意,但是假设我们有这个代码(我用Mono 2.10.2运行它,并用
gmcs
2.10.2.0编译):

使用系统;
公共类应用程序{
公共静态void Main(字符串[]args){
Func f=null;
var strs=新字符串[]{
“福”,
“酒吧”,
“南非兰特”
};
foreach(strs中的var str){
如果(“foo”。等于(str))
f=()=>str;
}
Console.WriteLine(f());//[1]:打印“zar”
foreach(strs中的var str){
var localStr=str;
如果(“foo”。等于(str))
f=()=>localStr;
}
Console.WriteLine(f());//[2]:打印“foo”
{int i=0;
用于(字符串str;istr;
}}
Console.WriteLine(f());//[3]:打印“zar”
}
}
似乎合乎逻辑的是,
[1]
打印的内容与
[3]
相同。但老实说,我不知怎么地希望它能像
[2]
一样打印出来。我不知怎的相信
[1]
的实现会更接近
[2]

问题:任何人都可以提供规范的参考,其中明确说明了
str
变量(甚至迭代器)是如何被
[1]
中的lambda捕获的


我想我要寻找的是
foreach
循环的确切实现。

以下情况发生在循环1和3中:

当前值分配给变量
str
。它始终是相同的变量,只是在每次迭代中使用不同的值。此变量由lambda捕获。由于lambda在循环完成后执行,因此它具有数组中最后一个元素的值


以下情况发生在循环2中:


当前值分配给新变量
localStr
。它总是一个新的变量来获得赋值。这个新变量由lambda捕获。因为循环的下一次迭代创建了一个新变量,所以捕获变量的值不会改变,并且因为它输出“foo”。

1/3和2之间的核心区别是被捕获变量的生存期。在1和3中,lambda捕获迭代变量
str
。在
for
foreach
循环中,在循环的生命周期中都有一个迭代变量。在循环结束时执行lambda时,它将以最终值执行:
zar

在2中,您将捕获一个局部变量,该变量的生命周期是循环的单个迭代。因此,您捕获了当时的值,即“foo”

我能给你的最好的参考是埃里克关于这个主题的博客文章


您要求参考本规范;相关位置见第8.8.4节,其中规定“foreach”回路相当于:

    V v;
    while (e.MoveNext()) {
        v = (V)(T)e.Current;
        embedded-statement
    }
请注意,值v是在while循环之外声明的,因此只有一个循环变量。然后由lambda关闭

更新 因为太多人遇到了这个问题,C#设计和编译器团队将C#5更改为具有这些语义

    while (e.MoveNext()) {
        V v = (V)(T)e.Current;
        embedded-statement
    }
这样就有了预期的行为——每次关闭一个不同的变量。从技术上讲,这是一个突破性的变化,但依赖于你所经历的怪异行为的人的数量希望非常少

请注意,在这方面,C#2、3和4现在与C#5不兼容。还请注意,更改仅适用于
foreach
,而不适用于
for
循环

有关详细信息,请参阅


评论员阿伯格梅尔说:

C#是唯一具有这种奇怪行为的语言

这句话绝对是假的。考虑下面的JavaScript:

var funcs = [];
var results = [];
for(prop in { a : 10, b : 20 })
{
  funcs.push(function() { return prop; });
  results.push(funcs[0]());
}

abergmeier,你能猜一下
结果的内容吗?

来自谷歌的人

我已经使用以下方法修复了lambda bug:

我改变了这个

for(int i=0;i<9;i++)
    btn.OnTap += () => { ChangeCurField(i * 2); };
for(inti=0;i{ChangeCurField(i*2);};
对此

for(int i=0;i<9;i++)
{
    int numb = i * 2;
    btn.OnTap += () => { ChangeCurField(numb); };
}
for(inti=0;i{ChangeCurField(numb);};
}

这迫使“麻木”变量是lambda的唯一变量,并且在此时生成,而不是在调用/生成lambda时生成。

第一个实现与
for
循环头中声明的变量的通常范围一致。但新实现试图适应以下事实:让我们期待并依赖于不一致的行为(这可能导致难以发现的bug——它发生在我身上).嗯,老实说,我对这个决定有复杂的感觉…@sinharaj:我也有复杂的感觉。一致性很好,因为它有助于你的直觉。但在这种情况下,一致性与人们对预期行为的直觉相反。人们不认为foreach的循环变量是一个变量e、 他们认为它是一个值(比如,你不能把它当作一个变量;你不能自己写它。)我不想有一个“愚蠢的一致性”这造成的伤害比防止的更大。@Eric:我投票赞成这一改变。现在的情况是,这是违反直觉的。捕获变量的lambda是在循环中声明的,因此人们希望它在那个时候具有值。然而,这只是一系列相关问题的一部分。
var I=0;操作a=()=>Debug.Write(i);i=2;a();
这里发生了与直觉相反的i的变化。我不知道在一个地方改变这种行为是否好,但让它保持不变
for(int i=0;i<9;i++)
{
    int numb = i * 2;
    btn.OnTap += () => { ChangeCurField(numb); };
}