123

いくつかのイベントを提供するクラスがあります。そのクラスはグローバルに宣言されますが、そのグローバル宣言に基づいてインスタンス化されません。それを必要とするメソッドで必要に応じてインスタンス化されます。

そのクラスがメソッドで必要になるたびに、インスタンス化され、イベント ハンドラーが登録されます。メソッドが範囲外になる前に、イベント ハンドラーを明示的に削除する必要がありますか?

メソッドが範囲外になると、クラスのインスタンスも範囲外になります。範囲外になるインスタンスに登録されたイベント ハンドラーを残すことは、メモリ フットプリントに影響しますか? (イベント ハンドラーが、クラス インスタンスが参照されなくなったと GC が認識しないようにするかどうか疑問に思っています。)

4

2 に答える 2

184

あなたの場合、すべて問題ありません。イベントハンドラーのターゲットをライブに保つイベントを発行するのはオブジェクトです。だから私が持っている場合:

publisher.SomeEvent += target.DoSomething;

その後publisherへの参照がありますtargetが、その逆はありません。

あなたの場合、パブリッシャーはガベージ コレクションの対象になるため (他に参照がない場合)、イベント ハンドラー ターゲットへの参照があるという事実は関係ありません。

トリッキーなケースは、パブリッシャーが長命であるが、サブスクライバーがそうであることを望んでいない場合です。その場合、ハンドラーのサブスクライブを解除する必要があります。たとえば、帯域幅の変更に関する非同期通知をサブスクライブできるデータ転送サービスがあり、転送サービス オブジェクトの寿命が長いとします。これを行う場合:

BandwidthUI ui = new BandwidthUI();
transferService.BandwidthChanged += ui.HandleBandwidthChange;
// Suppose this blocks until the transfer is complete
transferService.Transfer(source, destination);
// We now have to unsusbcribe from the event
transferService.BandwidthChanged -= ui.HandleBandwidthChange;

(イベント ハンドラーをリークしないようにするために、実際には finally ブロックを使用することをお勧めします。) サブスクライブを解除しなかった場合、 はBandwidthUI少なくとも転送サービスと同じくらい存続します。

個人的には、これに遭遇することはめったにありません。通常、イベントにサブスクライブすると、そのイベントのターゲットは、少なくとも発行者と同じくらい存続します。たとえば、フォームは、その上にあるボタンと同じくらい存続します。この潜在的な問題について知っておくことは価値がありますが、必要のないときに心配する人もいると思います。

編集:これは、Jonathan Dickinson のコメントに答えるものです。まず、同等の動作を明確に示すDelegate.Equals(object)のドキュメントを見てください。

次に、サブスクリプション解除の動作を示す短いが完全なプログラムを次に示します。

using System;

public class Publisher
{
    public event EventHandler Foo;

    public void RaiseFoo()
    {
        Console.WriteLine("Raising Foo");
        EventHandler handler = Foo;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
        else
        {
            Console.WriteLine("No handlers");
        }
    }
}

public class Subscriber
{
    public void FooHandler(object sender, EventArgs e)
    {
        Console.WriteLine("Subscriber.FooHandler()");
    }
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;
         publisher.RaiseFoo();
         publisher.Foo -= subscriber.FooHandler;
         publisher.RaiseFoo();
    }
}

結果:

Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers

(Mono および .NET 3.5SP1 でテスト済み)。

さらに編集:

これは、サブスクライバーへの参照がまだある間にイベント パブリッシャーを収集できることを証明するためです。

using System;

public class Publisher
{
    ~Publisher()
    {
        Console.WriteLine("~Publisher");
        Console.WriteLine("Foo==null ? {0}", Foo == null);
    }

    public event EventHandler Foo;
}

public class Subscriber
{
    ~Subscriber()
    {
        Console.WriteLine("~Subscriber");
    }

    public void FooHandler(object sender, EventArgs e) {}
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;

         Console.WriteLine("No more refs to publisher, "
             + "but subscriber is alive");
         GC.Collect();
         GC.WaitForPendingFinalizers();         

         Console.WriteLine("End of Main method. Subscriber is about to "
             + "become eligible for collection");
         GC.KeepAlive(subscriber);
    }
}

結果 (.NET 3.5SP1 では、Mono はここで少し奇妙な動作をしているように見えます。しばらく調べます):

No more refs to publisher, but subscriber is alive
~Publisher
Foo==null ? False
End of Main method. Subscriber is about to become eligible for collection
~Subscriber
于 2009-02-03T06:28:08.080 に答える
8

あなたの場合、あなたは大丈夫です。私はもともとあなたの質問を逆に読みました。発行者ではなく、サブスクライバーが範囲外になるということでした。イベント パブリッシャーが範囲外になると、サブスクライバーへの参照(もちろん、サブスクライバー自体ではありません!) はそれに伴い、明示的に削除する必要はありません。

私の元の回答は以下のとおりです。イベントサブスクライバーを作成し、サブスクライブを解除せずに範囲外にするとどうなるかについてです。あなたの質問には当てはまりませんが、履歴のために残しておきます。

クラスがまだイベント ハンドラーを介して登録されている場合は、引き続き到達可能です。それはまだ生きているオブジェクトです。イベント グラフに続く GC は、それが接続されていることを検出します。はい、イベント ハンドラーを明示的に削除する必要があります。

オブジェクトが元の割り当ての範囲外にあるからといって、GC の候補になるわけではありません。ライブ参照が残っている限り、それはライブです。

于 2009-02-03T05:37:41.770 に答える