例えば 下記のコードを実行すると 結果は 10
になる
using System; using System.Collections.Generic; public class C { public static void Main() { var actions = new List<A>(); for (var i= 0; i < 10; i++) { actions.Add(new A(() => {Console.Write(i);})); } actions[0].Invoke(); } } public class A { Action _action; public A(Action action) { _action = action; } public void Invoke() { _action.Invoke(); } }
そのため、index の値を正しく取得するには一度値をキャッシュしてから使う必要がある
for (var i= 0; i < 10; i++) { var index = i; actions.Add(new A(() => {Console.Write(index);})); }
プログラム的には 0番目は 0 だから 0 が出そうですが何故出ないのかが気になったので調査してみた
生成コードの調査
SharpLab より上記のコードから生成される最終的な C# コードを見てみる
using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Security.Permissions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] public class C { [CompilerGenerated] private sealed class <>c__DisplayClass0_0 { public int i; internal void <Main>b__0() { Console.Write(i); } } public static void Main() { List<A> list = new List<A>(); <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0(); <>c__DisplayClass0_.i = 0; while (<>c__DisplayClass0_.i < 10) { list.Add(new A(new Action(<>c__DisplayClass0_.<Main>b__0))); <>c__DisplayClass0_.i++; } list[0].Invoke(); } } public class A { private Action _action; public A(Action action) { _action = action; } public void Invoke() { _action(); } }
注目すべきは コンパイラが出力している c__DisplayClass0_0
クラスです
Action で実行される メソッドがになっており
internal void <Main>b__0() { Console.Write(i); }
このメソッドが参照する 値はループ前に生成されたインスタンスの i
になる
private sealed class <>c__DisplayClass0_0 { public int i;
そのため、ループ後に Action を呼び出すと 加算後 つまり i = 10 となっている <>c__DisplayClass0_.i
が呼び出されるため、結果が10になっていたことが分かる
生成コード調査その2
var index = i;
を追加した場合のコードを確認する
public class C { [CompilerGenerated] private sealed class <>c__DisplayClass0_0 { public int index; internal void <Main>b__0() { Console.Write(index); } } public static void Main() { List<A> list = new List<A>(); int num = 0; while (num < 10) { <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0(); <>c__DisplayClass0_.index = num; list.Add(new A(new Action(<>c__DisplayClass0_.<Main>b__0))); num++; } list[0].Invoke(); } }
c__DisplayClass0_0
が生成されているのは変わらないが
ループ内で c__DisplayClass0_0
のインスタンスが作成されている