簡単なコードを書いていたところ、このコンパイラエラーに気づきました
ラムダ式で反復変数を使用すると、予期しない結果が生じる可能性があります。
代わりに、ループ内にローカル変数を作成し、それに反復変数の値を割り当てます。
私はそれが何を意味するのかを知っており、大したことではなく、簡単に修正できます。
しかし、ラムダで反復変数を使用するのはなぜ悪い考えなのか疑問に思いました。
後でどのような問題を引き起こす可能性がありますか?
このコードを検討してください:
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、10回印刷されます。これは、すべてのデリゲートによってキャプチャされる変数が1つしかないためです。意外なのはこのような振る舞いです。
編集:あなたがC#ではなくVB.NETについて話しているのを見たばかりです。 VB.NETには、変数が反復間で値を維持する方法のために、さらに複雑なルールがあると思います。 Jared Parsonsによるこの投稿 関連する問題の種類に関する情報を提供します-2007年から戻っているため、実際の動作はそれ以降変更されている可能性があります。
ここでC#を意味すると仮定します。
これは、コンパイラがクロージャを実装する方法が原因です。反復変数canを使用すると、変更されたクロージャへのアクセスで問題が発生します(「できない」と言ったので、問題が発生することがあります。メソッド内の他の内容によっては発生しません。実際に変更されたクロージャにアクセスしたい場合もあります)。
より詳しい情報:
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
s)を使用してこれらの機能を実装する必要がありました。Func(Of T)
(つまり、Delegate
)インスタンスには、渡されたパラメーターを格納する方法がありません。Func(Of T)
は、メソッドが含まれるクラスのインスタンスを格納します。これは、ラムダ式に渡されたパラメーターを「記憶」するために使用される.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
ループスコープにはありません。
個人的には、コンパイラがこのためにどのようなコードを生成するのかを本当に知りたかったので、先に進んでJetBrainsDotPeekを使用しました。コンパイラで生成されたコードを取得し、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
Sub CompilerGenerated
の本体全体に対してclosureHelperClass
のインスタンスが1つしかないため、関数が中間のFor
ループインデックス値0,1を出力する方法がないことに注意してください。 、2,3(これらの値を格納する場所はありません)。このコードは、最終的なインデックス値(For
ループの後)を4回だけ出力します。
「しかし、なぜあなたは遅い答えを投稿したのですか?」