デリゲート内の変数の範囲
-
03-07-2019 - |
質問
次のことがかなり奇妙だとわかりました。繰り返しになりますが、私はほとんど同じ動的バグでクロージャーを使用しましたが、これは同じ「バグ」に疑われるべきではありません。以下はコンパイラを不幸にします:
VoidFunction t = delegate { int i = 0; };
int i = 1;
言う:
「i」という名前のローカル変数は このスコープで宣言されているのは、 「i」に異なる意味を与えます 「子」ですでに使用されています 他の何かを示すスコープ
つまり、これは基本的に、デリゲート内で宣言された変数には、宣言された関数のスコープが含まれることを意味します。私も関数を呼び出そうとしませんでした。少なくともCommon Lispには、変数を本当にローカルにしたい場合、変数に動的な名前を付けるべきだという機能があります。これは、リークしないマクロを作成するときに特に重要ですが、そのようなものもここで役立ちます。
では、この問題を回避するために他の人が何をしているのだろうか?
明確にするために、デリゲートで宣言した変数がデリゲートの で宣言された変数と干渉しない解決策を探しています。そして、デリゲートの前に宣言された変数をキャプチャできるようにしたいです。
解決
匿名メソッド(およびラムダ)が包含メソッドでスコープされたローカル変数とパラメーターを使用できるようにするためには、そのようにする必要があります。
回避策は、変数に異なる名前を使用するか、通常のメソッドを作成することです。
他のヒント
「クロージャ」無名関数によって作成されたものは、他の動的言語で作成されたものとは多少異なります(例としてJavascriptを使用します)。
function thing() {
var o1 = {n:1}
var o2 = {dummy:"Hello"}
return function() { return o1.n++; }
}
var fn = thing();
alert(fn());
alert(fn());
javascriptのこの小さなチャンクは1を表示し、次に2を表示します。o1変数はスコープチェーンに存在するため、o1変数にアクセスできます。ただし、匿名関数には完全に独立したスコープがあり、別のo1変数を作成して、スコープチェーンのさらに下にある他の変数を非表示にすることができます。また、チェーン全体のすべての変数が残っていることに注意してください。したがって、fn変数が関数参照を保持している限り、o2はオブジェクト参照を保持し続けます。
C#匿名関数と比較してみましょう:-
class C1 { public int n {get; set;} }
class C2 { public string dummy { get; set; } }
Func<int> thing() {
var o1 = new C1() {n=1};
var o2 = new C2() {dummy="Hello"};
return delegate { return o1.n++; };
}
...
Func<int> fn = thing();
Console.WriteLine(fn());
Console.WriteLine(fn());
この場合、匿名関数は、他の関数内の{}コードブロックの変数宣言( foreach
、で使用される)を超えて、真に独立したスコープを作成しません。 if
など)
同じルールが適用されるため、ブロック外のコードはブロック内で宣言された変数にアクセスできませんが、識別子も再利用できません。
匿名関数が作成された関数の外部に渡されると、クロージャが作成されます。Javascriptの例との違いは、匿名関数で実際に使用される変数のみが残るため、この場合、オブジェクトはby o2は、事が完了するとすぐにGCで利用可能になります。
また、次のようなコードからCS0136を取得します。
int i = 0;
if (i == 0) {
int i = 1;
}
&quot; i&quot;の2番目の宣言の範囲C ++のような言語には明確なものはありません。しかし、C#言語の設計者はそれを禁止することにしました。上記のスニペットを考えると、それはまだ悪い考えだと思いますか?たくさんの余分なコードを投げると、このコードをしばらく見つめることができますが、バグは見えません。
回避策は簡単で簡単です。別の変数名を考えてください。
デリゲートはデリゲートの外部の変数を参照できるためです:
int i = 1;
VoidFunction t = delegate { Console.WriteLine(i); };
正しく覚えていれば、コンパイラは、この動作を実現するために、匿名メソッドで参照される外部変数のクラスメンバーを作成します。
回避策は次のとおりです。
class Program
{
void Main()
{
VoidFunction t = RealFunction;
int i = 1;
}
delegate void VoidFunction();
void RealFunction() { int i = 0; }
}
実際には、エラーは匿名のデリゲートまたはラムダ式とは関係がないようです。次のプログラムをコンパイルしようとすると...
using System;
class Program
{
static void Main()
{
// Action t = delegate
{
int i = 0;
};
int i = 1;
}
}
...行にコメントするかどうかに関係なく、まったく同じエラーが発生します。 エラーヘルプは、非常によく似たケースを示しています。プログラマーが2つの変数を混同する可能性があるという理由で、両方のケースを禁止するのが合理的だと思います。