これは、コンパイラによって生成されたコードを確認することで最も簡単に理解できます。これは次のようになります。
public void AttachToAEvent()
{
_foo.AEvent += new EventHandler(this.Handler);
}
[CompilerGenerated]
private void Handler(object sender, EventArgs e)
{
this.UseBar(this._bar);
}
はっきりとわかるように、作成されるデリゲートはインスタンス-デリゲート(オブジェクトのインスタンスメソッドをターゲットにする)であるため、このオブジェクトインスタンスへの参照を保持する必要があります。
デリゲートは変数this._barをキャプチャするので、暗黙的にBのインスタンスを保持しますか?
実際には、anonymousメソッドはthis
(ではなくthis._bar
)をキャプチャします。生成されたコードからわかるように、構築されたデリゲートは実際にB
インスタンスへの参照を保持します。それはしなければなりません。デリゲートが実行されるたびに、他にどのようにフィールドをオンデマンドで読み取ることができますか?値ではなく、変数がキャプチャされることに注意してください。
私の場合、AのインスタンスはBのインスタンスよりもはるかに長持ちし、はるかに小さいので、これを行うことで「メモリリーク」が発生するのではないかと心配しています。
はい、あなたにはあらゆる理由があります。A
インスタンスが到達可能である限り、B
イベントサブスクライバーは引き続き到達可能です。弱いイベントに夢中になりたくない場合は、これを書き直して、ハンドラーが不要になったときに登録が解除されるようにする必要があります。
_barがAttachToAEventメソッドのローカル変数である場合は異なりますか?
はい、そうなります。キャプチャされた変数は、bar
ではなくローカルになりthis
ます。しかし、それがインスタンスメソッドであると仮定するUseBar
と、「問題」(そのように考えたくない場合)はさらに悪化します。B
コンパイラは、ローカルインスタンスとそれを含むオブジェクトインスタンスの両方を「記憶」するイベントリスナーを生成する必要があります。
これは、クロージャオブジェクトを作成し、それ(実際にはそのインスタンスメソッド)をデリゲートのターゲットにすることで実現されます。
public void AttachToAEvent(int _bar)
{
Closure closure = new Closure();
closure._bar = _bar;
closure._bInstance = this;
_foo.AEvent += new EventHandler(closure.Handler);
}
[CompilerGenerated]
private sealed class Closure
{
public int _bar;
public B _bInstance;
public void Handler(object sender , EventArgs e)
{
_bInstance.UseBar(this._bar);
}
}