我只是写一些简单的代码,发现这个错误编译
在一个lambda表达式使用迭代变量可以具有意想不到的效果。
取而代之的是,建立在循环中的局部变量,并为其分配迭代变量的值。
我知道这意味着什么,我可以很容易地解决这个问题,不是一个大问题。
但我不知道为什么这是一个坏主意的拉姆达使用迭代变量?
我可能会导致什么问题以后?
我只是写一些简单的代码,发现这个错误编译
在一个lambda表达式使用迭代变量可以具有意想不到的效果。
取而代之的是,建立在循环中的局部变量,并为其分配迭代变量的值。
我知道这意味着什么,我可以很容易地解决这个问题,不是一个大问题。
但我不知道为什么这是一个坏主意的拉姆达使用迭代变量?
我可能会导致什么问题以后?
考虑以下代码:
List<Action> actions = new List<Action>();
for (int i = 0; i < 10; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (Action action in actions)
{
action();
}
你会想到这个打印? 答案显然是0 ... 9 - 但实际上它打印10,十次。 这是因为这里有一个被所有与会代表捕获变量。 它的这种行为是意外。
编辑:我刚刚看到你在谈论VB.NET,而不是C#。 我相信,VB.NET有更为复杂的规则,由于变量在各次迭代维持其值的方式。 本帖由贾里德·帕森斯给人的那种涉及困难的一些信息-虽然这是从2007年回来,所以从那时起实际行为可能已经改变。
假设你的意思是C#在这里。
这是因为编译器实现封闭的方式。 使用迭代变量可能会导致访问修改关闭(注意一个问题,我说“可以”而不是“会”造成一个问题,因为有时不取决于还有什么是在方法发生,有时候你真的想访问修改闭合)。
更多信息:
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/03/687529.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
局部变量:范围与寿命(加闭包) (存档2010)
(重点煤矿)
会发生什么事在这种情况下,我们使用一个封闭。 闭包是一种特殊的结构,居住其中包含了需要用其他方法被称为局部变量的方法之外。 当查询是指一个局部变量(或参数),该变量是由封闭捕获并给变量的所有引用将被重定向到闭合。
当你正在考虑关闭在.NET中是如何工作的,我建议牢记这些要点,这是设计者们什么时候被执行此功能一起工作:
Delegate
秒。 Func(Of T)
(即Delegate
)情况下没有办法来存储传递到它们的参数。 Func(Of T)
做存储类的实例中的方法是的一部分。 这是大道用于“记住”传递到Lambda表达式参数的.NET框架。 那么让我们来看看!
因此,让我们说你写了一些这样的代码:
' Prints 4,4,4,4
Sub VBDotNetSample()
Dim funcList As New List(Of Func(Of Integer))
For indexParameter As Integer = 0 To 3
'The compiler says:
' Warning BC42324 Using the iteration variable in a lambda expression may have unexpected results.
' Instead, create a local variable within the loop and assign it the value of the iteration variable
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
循环范围。
就个人而言,我真的很想看到这个产生什么样的代码编译器,所以我继续使用JetBrains公司DotPeek。 我把编译器生成的代码和手工翻译回VB.NET。
注释和变量名矿。 该代码是在不影响代码的行为方式略有简化。
Module Decompiledcode
' Prints 4,4,4,4
Sub CompilerGenerated()
Dim funcList As New List(Of Func(Of Integer))
'***********************************************************************************************
' There's only one instance of the closureHelperClass for the entire Sub
' That means that all the iterations of the for loop below are referencing
' the same class instance; that means that it can't remember the value of Local_indexParameter
' at each iteration, and it only remembers the last one (4).
'***********************************************************************************************
Dim closureHelperClass As New ClosureHelperClass_CompilerGenerated
For closureHelperClass.Local_indexParameter = 0 To 3
' NOTE that it refers to the Lambda *instance* method of the ClosureHelperClass_CompilerGenerated class,
' Remember that delegates implicitly carry the instance of the class in their Target
' property, it's not just referring to the Lambda method, it's referring to the Lambda
' method on the closureHelperClass instance of the class!
Dim closureHelperClassMethodFunc As Func(Of Integer) = AddressOf closureHelperClass.Lambda
funcList.Add(closureHelperClassMethodFunc)
Next
'closureHelperClass.Local_indexParameter is 4 now.
'Run each stored lambda expression (on the Delegate's Target, closureHelperClass)
For Each lambdaFunc As Func(Of Integer) in funcList
'The return value will always be 4, because it's just returning closureHelperClass.Local_indexParameter.
Dim retVal_AlwaysFour As Integer = lambdaFunc()
Console.Write($"{retVal_AlwaysFour}")
Next
End Sub
Friend NotInheritable Class ClosureHelperClass_CompilerGenerated
' Yes the compiler really does generate a class with public fields.
Public Local_indexParameter As Integer
'The body of your lambda expression goes here, note that this method
'takes no parameters and uses a field of this class (the stored parameter value) instead.
Friend Function Lambda() As Integer
Return Me.Local_indexParameter
End Function
End Class
End Module
注意如何有只有一个实例closureHelperClass
为整个身体Sub CompilerGenerated
,所以也没有办法,该函数可以打印中间For
0,1,2,3循环索引值(有没有来存储这些值的地方) 。 代码只打印4,最后索引值(后For
环)四次。
“但JRH你为什么要发布一个迟到的回答?”