これはループとは関係ありません。
この動作は() => variable * 2
、外側のスコープvariable
が実際にはラムダの内側のスコープで定義されていないラムダ式を使用するためにトリガーされます。
ラムダ式(C#3 +およびC#2の無名メソッド)でも、実際のメソッドが作成されます。これらのメソッドに変数を渡すと、いくつかのジレンマが生じます(値渡し?参照渡し?C#は参照渡しになりますが、これにより、参照が実際の変数より長く存続するという別の問題が発生します)。これらすべてのジレンマを解決するためにC#が行うことは、ラムダ式で使用されるローカル変数に対応するフィールドと実際のラムダメソッドに対応するメソッドを持つ新しいヘルパークラス(「クロージャー」)を作成することです。variable
コードの変更は実際にはその変更に変換されますClosureClass.variable
そのため、whileループはClosureClass.variable
10に達するまでを更新し続け、その後、forループはすべて同じで動作するアクションを実行しますClosureClass.variable
。
期待どおりの結果を得るには、ループ変数と、閉じられる変数を分離する必要があります。これを行うには、別の変数を導入します。つまり、
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
var t = variable; // now t will be closured (i.e. replaced by a field in the new class)
actions.Add(() => t * 2);
++variable; // changing variable won't affect the closured variable t
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
クロージャーを別のメソッドに移動して、この分離を作成することもできます。
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
actions.Add(Mult(variable));
++variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Multをラムダ式として実装できます(暗黙のクロージャー)。
static Func<int> Mult(int i)
{
return () => i * 2;
}
または実際のヘルパークラスで:
public class Helper
{
public int _i;
public Helper(int i)
{
_i = i;
}
public int Method()
{
return _i * 2;
}
}
static Func<int> Mult(int i)
{
Helper help = new Helper(i);
return help.Method;
}
いずれの場合でも、「クロージャ」はループに関連する概念ではなく、匿名メソッド/ラムダ式のローカルスコープ変数の使用に関係します。ただし、ループの慎重な使用によってクロージャトラップが示される場合もあります。