次のようなコードがあります。
struct A
{
void SomeMethod()
{
var items = Enumerable.Range(0, 10).Where(i => i == _field);
}
int _field;
}
...そして、次のコンパイラエラーが発生します:
構造体内の匿名メソッドは、'this' のインスタンス メンバーにアクセスできません。
ここで何が起こっているのか誰でも説明できますか。
次のようなコードがあります。
struct A
{
void SomeMethod()
{
var items = Enumerable.Range(0, 10).Where(i => i == _field);
}
int _field;
}
...そして、次のコンパイラエラーが発生します:
構造体内の匿名メソッドは、'this' のインスタンス メンバーにアクセスできません。
ここで何が起こっているのか誰でも説明できますか。
変数は参照によってキャプチャされます (実際には値型であっても、ボックス化が行われます)。
ただし、this
ValueType (構造体) はボックス化できないため、キャプチャできません。
Eric Lippert は、ValueTypes をキャプチャすることの驚きに関する素晴らしい記事を書いています。リンクを見つけさせてください
Chris Sinclair によるコメントへの応答に注意してください。
簡単な修正として、構造体をローカル変数に保存できます。
A thisA = this; var items = Enumerable.Range(0, 10).Where(i => i == thisA._field);
– Chris Sinclair 4 分前
これにより驚くべき状況が生じることに注意してください: のアイデンティティはと同じでthisA
はありませんthis
。より明示的には、ラムダをより長く保持することを選択した場合、呼び出された実際のインスタンスではなく、ボックス化されたコピー thisA
が参照によってキャプチャされます。SomeMethod
匿名メソッドがある場合、それは新しいクラスにコンパイルされ、そのクラスには1つのメソッド(定義したメソッド)があります。また、anonymousメソッドの範囲外で使用した各変数への参照も含まれます。これは、その変数のコピーではなく、参照であることを強調することが重要です。ことわざにあるように、「ラムダは値ではなく変数を閉じます」。つまり、ラムダのスコープ外の変数を閉じて、匿名メソッドを定義した後(ただし、呼び出す前に)その変数を変更すると、呼び出したときに変更された値が表示されます)。
それで、そのすべてのポイントは何ですか。値型である構造体を閉じるthis
と、ラムダが構造体よりも長持ちする可能性があります。匿名メソッドは構造体ではなくクラスに含まれるため、ヒープ上に配置され、必要な限り存続します。また、必要に応じて、そのクラスへの参照を(直接的または間接的に)自由に渡すことができます。
ここで定義したタイプの構造体を持つローカル変数があると想像してください。この名前付きメソッドを使用してラムダを生成し、しばらくの間、クエリが返されると仮定しますitems
(メソッドが返される代わりにvoid
)。次に、そのクエリを(ローカルではなく)別のインスタンスに格納し、しばらくしてから別のメソッドでそのクエリを繰り返すことができます。ここで何が起こりますか?本質的には、スコープ内になくなった後は、スタック上にあった値型への参照を保持していました。
どういう意味ですか?答えは、私たちにはわかりません。(リンクを確認してください。これは私の議論の核心です。)データはたまたま同じである可能性があり、ゼロにされている可能性があり、まったく異なるオブジェクトで埋められている可能性があり、知る方法はありません。C#は、言語として、このようなことを防ぐために非常に長い時間を費やしています。CやC++などの言語は、自分の足を撃つのを阻止しようとはしません。
さて、この特定のケースでは、this
参照する範囲外でラムダを使用しない可能性がありますが、コンパイラはそれを認識していません。ラムダを作成できる場合は、それを長持ちさせる可能性のある方法で公開するかどうかを判断するthis
ため、この問題を防ぐ唯一の方法は、実際には問題のないいくつかのケースを許可しないことです。