変数キャプチャがクロージャを作成するために変数をどのように取り込むかについての投稿を数え切れないほど見ましたが、それらはすべて特定の詳細が不足しているようで、全体を「コンパイラマジック」と呼んでいます。
私は次の明確な説明を探しています:
私の好みは、値とポインター(内部で起こることの中心に近い)の観点からの答えですが、値と参照を含む明確な答えも受け入れます。
例を使用してキャプチャがどのように機能するかを示すのがおそらく最も簡単です...
単一の変数をキャプチャするラムダ式を使用するコードを次に示します。
using System;
class Test
{
static void Main()
{
Action action = CreateShowAndIncrementAction();
action();
action();
}
static Action CreateShowAndIncrementAction()
{
Random rng = new Random();
int counter = rng.Next(10);
Console.WriteLine("Initial value for counter: {0}", counter);
return () =>
{
Console.WriteLine(counter);
counter++;
};
}
}
これがコンパイラーが行っていることです。ただし、C#では実際には発生し得ない「言いようのない」名前を使用する点が異なります。
using System;
class Test
{
static void Main()
{
Action action = CreateShowAndIncrementAction();
action();
action();
}
static Action CreateShowAndIncrementAction()
{
ActionHelper helper = new ActionHelper();
Random rng = new Random();
helper.counter = rng.Next(10);
Console.WriteLine("Initial value for counter: {0}", helper.counter);
// Converts method group to a delegate, whose target will be a
// reference to the instance of ActionHelper
return helper.DoAction;
}
class ActionHelper
{
// Just for simplicity, make it public. I don't know if the
// C# compiler really does.
public int counter;
public void DoAction()
{
Console.WriteLine(counter);
counter++;
}
}
}
ループで宣言された変数をキャプチャすると、ループの反復ごとにActionHelper
の新しいインスタンスが作成されるため、変数のさまざまな「インスタンス」を効果的にキャプチャできます。
さまざまなスコープから変数をキャプチャすると、さらに複雑になります...そのような詳細レベルが本当に必要な場合、またはコードを記述してReflectorで逆コンパイルし、それに従うことができるかどうかをお知らせください:)
方法に注意してください:
編集:これは、変数を共有する2人のデリゲートの例です。 1つのデリゲートはcounter
の現在の値を示し、もう1つのデリゲートはそれをインクリメントします。
using System;
class Program
{
static void Main(string[] args)
{
var Tuple = CreateShowAndIncrementActions();
var show = Tuple.Item1;
var increment = Tuple.Item2;
show(); // Prints 0
show(); // Still prints 0
increment();
show(); // Now prints 1
}
static Tuple<Action, Action> CreateShowAndIncrementActions()
{
int counter = 0;
Action show = () => { Console.WriteLine(counter); };
Action increment = () => { counter++; };
return Tuple.Create(show, increment);
}
}
...そして拡張:
using System;
class Program
{
static void Main(string[] args)
{
var Tuple = CreateShowAndIncrementActions();
var show = Tuple.Item1;
var increment = Tuple.Item2;
show(); // Prints 0
show(); // Still prints 0
increment();
show(); // Now prints 1
}
static Tuple<Action, Action> CreateShowAndIncrementActions()
{
ActionHelper helper = new ActionHelper();
helper.counter = 0;
Action show = helper.Show;
Action increment = helper.Increment;
return Tuple.Create(show, increment);
}
class ActionHelper
{
public int counter;
public void Show()
{
Console.WriteLine(counter);
}
public void Increment()
{
counter++;
}
}
}