闭包 Lambda 在 for 和 foreach c 中的特定性#

Specific of Closure Lambda in for and foreach c#

提问人:Denis Sivtsov 提问时间:2/7/2023 最后编辑:Denis Sivtsov 更新时间:2/13/2023 访问量:577

问:

我阅读了有关此主题的不同文章 Eric Lippert 的博客和其他 这里 并且知道为什么这两个代码的工作方式不同

int[] values = { 7, 9, 13 };
List<Action> f = new List<Action>();
foreach (var value in values)
    f.Add( () => Console.WriteLine("foreach value: " + value));
foreach (var item in f)
    item();

f = new List<Action>();
for (int i = 0; i < values.Length; i++)
    f.Add(() => Console.WriteLine("for value: " + ((i < values.Length) ? values[i] : i)));
foreach (var item in f)
    item();

但是我没有找到明确的解释为什么最终(从编译器 c# 版本 5 开始)决定“foreach 循环变量在逻辑上将位于循环主体内,因此闭包每次都会获得新的副本。 因为旧的实现提供了使用闭包 Lambda 的更多自由(“按值或按引用”),但要求程序员谨慎使用它,如果您需要 foreach 的当前实现,您应该使用以下代码:

foreach(var v in values) 
{ 
  var v2 = v;   
  funcs.Add( ()=>v2 ); 
}

问题:

问题1.我想知道为什么最后决定改变 foreach 的实现(我读到的利弊,这是 50/50 Eric Lippert 的博客)?

问题2.在“for”循环的情况下,它是一个超出循环操作范围的值(这会产生一个非常具体的情况,其中 lambda 得到一个你永远不会在循环中获得的值),为什么它“失控”,因为它是一个非常容易出错的情况?(问题 2 更具修辞性,因此可以跳过)

附加说明(针对 Q1) 了解选择此实现的原因会很有趣 - 为什么 C# 的开发人员从版本 5 开始更改了 foreach 循环的闭包原则。或者为什么他们为 foreach 循环做了,但没有为 for 做

C# for 循环 lambda foreach 闭包

评论

1赞 David L 2/7/2023
我不明白除了 Eric Lippert 的链接答案和博客之外,您在寻找一个令人满意的答案。
0赞 Denis Sivtsov 2/7/2023
他很好地解释了存在的问题以及他们是如何做到的,但没有解释他们为什么这样做,对我来说,深入了解 c# 细节很有趣
0赞 Denis Sivtsov 2/7/2023
在阅读 stackoverflow 的额外迭代之后,我发现了可以从这里给出 Q1 答案的问题 - 最终决定不是在技术原因下做出的,而是基于心理原因 - 开始使用 LINQ & foreach(非常流行的连接)的大多数初学者开发人员最初希望 foreach 中的闭包 Lambda 将“按值”完成, 就像在 Java 7 之前使用变量一样final
0赞 TylerH 2/8/2023
这回答了你的问题吗?Closure 和 Linq
0赞 Denis Sivtsov 2/9/2023
@TylerH 不,这是一个有用的信息(“关于它如何在简单示例中工作”),但没有解释为什么它以这种方式实现(“for cycle”闭包 Lambda “by ref” 和“foreach” 闭包“by ”val“)。埃里克·利珀特(Eric Lippert)在他的博客中很好地解释了这个问题的问题,但我没有找到为什么“foreach”被更改?在上面,我解释了为什么可能会这样做,但对我来说,有兴趣从更多有经验的 c# 开发人员那里获得解释

答:

8赞 Eric Lippert 2/13/2023 #1

我想知道为什么最后决定改变foreach

因为 (1) 用户坚信编译器的行为充其量是出乎意料的,最坏的情况是完全错误的,并且 (2) 没有令人信服的理由来保留奇怪的行为。导致设计团队不进行重大更改的最大因素是“因为真正的代码依赖于当前的行为”,但依赖于该行为的真实代码可能是错误的!

这是一个相当容易做出的决定。很多人抱怨这种行为;根本没有人抱怨修复。这是一个很好的电话。

为什么他们为 The Loop 做了,但没有为 .foreachfor

(1)人们没有抱怨这个循环,(2)改变会困难得多,(3)改变更有可能产生现实世界的中断。for

人们可以合理地期望 a 的“循环变量”不是“实数”变量。你永远不会改变它;运行时会为您更改它:foreach

foreach(char c in "ABCDEFG")
{
  c = 'X'; // This is illegal! You cannot treat c as a variable.
}

但对于循环的循环变量来说,情况并非如此;它们确实是变量。for

    for(int i = 0; i < 10; i += 1)
      i = 11; // weird but legal!

for循环要复杂得多。您可以有多个变量,它们可以任意更改值,它们可以在循环外声明,等等。最好不要冒着破坏某人的风险,改变这些变量在循环中的处理方式。