ちょっと悩ましい問題に気付いたのでメモっておきます。
対象はC# 3.0です。
メモなので用語等不適切かもしれません。
デリゲート型フィールドをラムダ式で初期化する §
リスト1は通るコードです。
コンストラクタでデリゲート型フィールドをラムダ式で初期化しています。
リスト1 コンストラクタで初期化 §
using System;
class A
{
private int x = 0;
public Action d;
public A()
{
d = () =>
{
Console.WriteLine(x);
};
}
}
class Program
{
static void Main(string[] args)
{
A a = new A();
a.d();
}
}
初期化式を移動させる §
ラムダ式をコンストラクタからフィールドを直接初期化する位置に移動させると、コンパイルが通らなくなります。
リスト2 フィールドを直接初期化 §
using System;
class A
{
private int x = 0;
public Action d = () =>
{
Console.WriteLine(x);
};
}
class Program
{
static void Main(string[] args)
{
A a = new A();
a.d();
}
}
エラー 1 フィールド初期化子は、静的でないフィールド、メソッド、またはプロパティ 'A.x' を参照できません d:\w\test\ConsoleApplication73\ConsoleApplication73\Program.cs 8 27 ConsoleApplication73
何が問題なのか §
リスト1はthisをキャプチャしているからメンバにアクセスでき、リスト2はキャプチャすべきthisが無いからメンバにアクセスできない、という話ではないようです。Reflectorで見る限り、キャプチャという手順無しでthisへのアクセスは可能になっているようです。(デリゲート型はインスタンスの情報を持つので、それを経由すれば上位環境のthisをラムダ式に引き継げる……のだと思う。たぶん)
つまり、初期化タイミングが違うだけで、メンバにアクセスできる、できないの差が出ます。
別の例 §
オブジェクトの初期化構文を使っても、やはり初期化はできませんが、理由は違います。このラムダ式はクラスBではなくクラスCに含まれるので、そもそもBに対するthisはあり得ません。
リスト3 オブジェクトの初期化構文を使う §
using System;
class B
{
public int x = 0;
public Action SomeMethod;
}
class C
{
public B b = new B()
{
SomeMethod = () =>
{
Console.WriteLine(x);
},
};
}
class Program
{
static void Main(string[] args)
{
C c = new C();
c.b.SomeMethod();
}
}
エラー 1 エラー 1 名前 'x' は現在のコンテキスト内に存在しません。 d:\w\test\ConsoleApplication75\ConsoleApplication75\Program.cs 18 39 ConsoleApplication75
で、何が問題なの? §
当面は以上の確認とメモだけ。