12
class A
{
   public event EventHandler AEvent;
}
class B
{
   private A _foo;
   private int _bar;

   public void AttachToAEvent()
   {
      _foo.AEvent += delegate()
      {
         ...
         UseBar(_bar);
         ...
      }
   }
} 

delegate変数をキャプチャするのでthis._bar、暗黙的にのインスタンスを保持しますBか?のインスタンスはB、イベントハンドラーを介して参照され、?のインスタンスによってキャプチャされた変数になりますAか?

メソッド_barのローカル変数だったら違うのでしょうか?AttachToAEvent

私の場合、インスタンスのA寿命ははるかに長く、のインスタンスよりもはるかに小さいので、これBを行うことで「メモリリーク」が発生するのではないかと心配しています。

4

3 に答える 3

15

アニの答えは正しいです。いくつかの詳細の要約と追加:

デリゲートは変数this._barをキャプチャするので、暗黙的にBのインスタンスを保持しますか?

はい。「これ」をキャプチャします。

Bのインスタンスはイベントハンドラーを介して参照され、Aのインスタンスによって変数がキャプチャされますか?

はい。

_barがAttachToAEventメソッドのローカル変数である場合は異なりますか?

はい。その場合、クロージャーオブジェクトはローカルを保持します。ローカルは閉鎖の分野として実現されるでしょう。

私の場合、AのインスタンスはBのインスタンスよりもはるかに長持ちし、はるかに小さいので、これを行うことで「メモリリーク」が発生するのではないかと心配しています。

あなたは絶対に心配する権利があります。あなたの状況はすでに悪いですが、実際には、2つの匿名関数を使用している場合、状況はかなり悪化する可能性があります。現在、同じローカル変数宣言スペース内のすべての無名関数は共通のクロージャを共有しています。つまり、すべての閉じられた外部変数( "this"を含む)の有効期間は、それらすべての中で最も寿命が長い限り延長されます。詳細については、このテーマに関する私の記事を参照してください。

http://blogs.msdn.com/b/ericlippert/archive/2007/06/06/fyi-c-and-vb-closures-are-per-scope.aspx

これは、C#の仮想的な将来のバージョンで修正されることを望んでいます。1つの大きなクロージャを作成する代わりに、クロージャをより適切に分割することができます。しかし、それはすぐには起こらないでしょう。

さらに、C#5の「非同期/待機」機能は、地元の人々が予想よりも長生きする状況を悪化させる可能性もあります。私たちの誰もがこれにわくわくすることはありませんが、彼らが言うように、完璧は素晴らしいものの敵です。非同期ブロックのcodegenを微調整して状況を改善する方法についてはいくつかのアイデアがありますが、約束はありません。

于 2011-12-07T16:10:38.513 に答える
10

これは、コンパイラによって生成されたコードを確認することで最も簡単に理解できます。これは次のようになります。

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);
    }
}
于 2011-12-07T15:54:41.180 に答える
0

匿名メソッドをイベントに追加し、それを尊重したい場合は、イベントをnullに設定するか、デリゲートをリストに保存して、後でイベントから「-=」できるようにする必要があります。

ただし、イベントにアタッチされているデリゲートで参照されているオブジェクトから「メモリリーク」が発生する可能性があります。

于 2011-12-07T15:14:31.433 に答える