为什么在lambda表达式中使用迭代变量是不好的
为什么在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表达式中引用该局部变量。
使用迭代变量在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)
为什么在lambda表达式中使用迭代变量是不好的?
在上面的代码中,我们期望输出的结果是0到9,但实际上输出的是10,重复10次。这是因为lambda表达式中捕获的是同一个变量,而不是每次迭代中的不同变量。这种行为是出乎意料的。
lambda表达式不一定在循环时被评估,当它们被调用时,迭代变量可能已超出作用域、未分配或具有最终值(甚至超出循环限制)。这就是为什么不能在lambda表达式中使用迭代变量的原因。
解决这个问题的一种方法是在lambda表达式中使用一个局部变量来保存迭代变量的值。这样可以确保每次迭代时,lambda表达式都可以正确地访问到迭代变量的值。
考虑下面的修改代码:
Listactions = 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的博文中介绍的有所不同。