为什么在lambda表达式中使用迭代变量是不好的

23 浏览
0 Comments

为什么在lambda表达式中使用迭代变量是不好的

我正在写一些快速代码时,注意到了这个编译器错误:\n

\n在lambda表达式中使用迭代变量可能会导致意外结果。\n相反,在循环内创建一个局部变量,并将其赋值为迭代变量的值。\n

\n我知道它的意思,而且可以很容易地修复它,没什么大不了的。\n但我想知道为什么在lambda中使用迭代变量是个坏主意?\n我可能会引起什么问题?

0
0 Comments

为什么在lambda表达式中使用迭代变量是不好的呢?这个问题的原因是,lambda表达式中的变量会被捕获并重定向到闭包中,而不是在循环范围内。因此,在循环中使用迭代变量会导致意外的结果。解决方法是在循环内部创建一个局部变量,并将迭代变量的值赋给它,然后在lambda表达式中引用该局部变量。

闭包是指一个特殊的结构,它存在于包含需要被其他方法引用的局部变量的方法之外。当一个查询引用一个局部变量(或参数)时,该变量被闭包捕获,所有对该变量的引用都被重定向到闭包。

在.NET中,闭包是通过使用现有的工具,即类和委托,来实现的。具体来说,Func(Of T)(即委托)实例无法存储传递给它们的参数。但是,Func(Of T)确实存储了方法所属类的实例。这是.NET框架用来“记住”传递给lambda表达式的参数的途径。

下面是一个示例代码:

Sub VBDotNetSample()
    Dim funcList As New List(Of Func(Of Integer))
    For indexParameter As Integer = 0 To 3
        funcList.Add(Function() indexParameter)
    Next
    For Each lambdaFunc As Func(Of Integer) In funcList
        Console.Write($"{lambdaFunc()}")
    Next
End Sub

预期输出应该是0,1,2,3,但实际上输出的是4,4,4,4。这是因为indexParameter已经在Sub VBDotNetSample()的作用域中被“捕获”,而不是在For循环的作用域中。

反编译示例代码后,可以看到编译器生成的代码。在整个Sub CompilerGenerated的作用域中只有一个closureHelperClass实例,因此函数无法打印出中间的For循环索引值0,1,2,3(没有地方存储这些值),代码只打印出4,即For循环的最后一个索引值,重复四次。

使用迭代变量在lambda表达式中是不好的,因为它会导致意外的结果。解决方法是在循环内部创建一个局部变量,并将迭代变量的值赋给它,然后在lambda表达式中引用该局部变量。

0
0 Comments

使用迭代变量在lambda表达式中是不好的,原因是编译器实现闭包的方式。使用迭代变量可能会导致访问被修改的闭包的问题(注意,我说的是“可能”而不是“一定”会导致问题,因为有时候根据方法中的其他内容,有时候实际上希望访问修改后的闭包)。

更多信息请参考以下链接:

- [http://blogs.msdn.com/abhinaba/archive/2005/10/18/482180.aspx](http://blogs.msdn.com/abhinaba/archive/2005/10/18/482180.aspx)

- [http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx](http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx)

- [http://blogs.msdn.com/oldnewthing/archive/2006/08/03/687529.aspx](http://blogs.msdn.com/oldnewthing/archive/2006/08/03/687529.aspx)

- [http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx](http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx)

这并不是“每个方法一个闭包”的情况,情况比这更复杂。链接已经无法访问,但您仍然可以在这里找到它们:

- [https://devblogs.microsoft.com/oldnewthing/2006/08/page/4](https://devblogs.microsoft.com/oldnewthing/2006/08/page/4)

- “The implementation of anonymous methods and its consequences” (Raymond Chen / Old New Thing Blog) 的第1部分 [https://devblogs.microsoft.com/oldnewthing/20060802-00/?p=30263](https://devblogs.microsoft.com/oldnewthing/20060802-00/?p=30263)

- 第2部分 [https://devblogs.microsoft.com/oldnewthing/20060803-00/?p=30253](https://devblogs.microsoft.com/oldnewthing/20060803-00/?p=30253)

- 第3部分 [https://devblogs.microsoft.com/oldnewthing/20060804-00/?p=30233](https://devblogs.microsoft.com/oldnewthing/20060804-00/?p=30233)

0
0 Comments

为什么在lambda表达式中使用迭代变量是不好的?

在上面的代码中,我们期望输出的结果是0到9,但实际上输出的是10,重复10次。这是因为lambda表达式中捕获的是同一个变量,而不是每次迭代中的不同变量。这种行为是出乎意料的。

lambda表达式不一定在循环时被评估,当它们被调用时,迭代变量可能已超出作用域、未分配或具有最终值(甚至超出循环限制)。这就是为什么不能在lambda表达式中使用迭代变量的原因。

解决这个问题的一种方法是在lambda表达式中使用一个局部变量来保存迭代变量的值。这样可以确保每次迭代时,lambda表达式都可以正确地访问到迭代变量的值。

考虑下面的修改代码:

List actions = new List();
for (int i = 0; i < 10; i++)
{
    int temp = i; // 使用一个临时变量保存迭代变量的值
    actions.Add(() => Console.WriteLine(temp));
}
foreach (Action action in actions)
{
    action();
}

通过使用一个临时变量来保存迭代变量的值,我们可以确保每个lambda表达式都可以正确地访问到迭代变量的值,输出结果会是我们期望的0到9。

需要注意的是,以上解决方法适用于C#,在VB.NET中可能存在更复杂的规则,具体情况可能与2007年Jared Parsons的博文中介绍的有所不同。

0