54

WinForms コントロールからのイベントを処理するクラスがあります。ユーザーが何をしているかに基づいて、クラスの 1 つのインスタンスを参照し、同じイベントを処理するために新しいインスタンスを作成しています。最初にイベントから古いインスタンスのサブスクライブを解除する必要があります - 簡単です。できれば非独占的な方法でこれを行いたいのですが、これは IDisposable の仕事のようです。ただし、ほとんどのドキュメントでは、管理されていないリソースを使用する場合にのみ IDisposable を推奨しているため、ここでは適用されません。

IDisposable を実装し、Dispose() でイベントの登録を解除すると、その意図を逸脱することになりますか? 代わりに Unsubscribe() 関数を提供してそれを呼び出す必要がありますか?


編集:これは、私が何をしているのかを示すダミーコードです(IDisposableを使用)。私の実際の実装は、いくつかの独自のデータ バインディングに関連しています (長い話)。

class EventListener : IDisposable
{
    private TextBox m_textBox;

    public EventListener(TextBox textBox)
    {
        m_textBox = textBox;
        textBox.TextChanged += new EventHandler(textBox_TextChanged);
    }

    void textBox_TextChanged(object sender, EventArgs e)
    {
        // do something
    }

    public void Dispose()
    {
        m_textBox.TextChanged -= new EventHandler(textBox_TextChanged);
    }
}

class MyClass
{
    EventListener m_eventListener = null;
    TextBox m_textBox = new TextBox();

    void SetEventListener()
    {
        if (m_eventListener != null) m_eventListener.Dispose();
        m_eventListener = new EventListener(m_textBox);
    }
}

実際のコードでは、「EventListener」クラスがより関与しており、各インスタンスは一意に重要です。これらをコレクションで使用し、ユーザーがクリックするたびに作成/破棄します。


結論

少なくとも今のところ、gbjbaanb の回答を受け入れています。使い慣れたインターフェイスを使用することの利点は、アンマネージ コードが含まれていない場合に使用することの潜在的な欠点を上回ると思います (このオブジェクトのユーザーは、どうしてそれを知ることができるでしょうか?)。

誰かが同意しない場合は、投稿/コメント/編集してください. IDisposable に対してより良い議論ができる場合は、受け入れられた答えを変更します。

4

9 に答える 9

46

はい、がんばってください。IDisposable は管理されていないリソースに対してのみ実装されていると考える人もいますが、そうではありません。人々がそれを使用する他の理由を考えることができなかったので、このアイデアを獲得したと思います. パフォーマンスの問題であり、GC が適切に処理するのは容易ではないファイナライザーとは異なります。

dispose メソッドに整理コードを入れます。参照を元に戻すことを覚えようとするよりも、より明確でクリーンになり、メモリリークを防ぐ可能性が大幅に高くなり、正しく使用するのが簡単になります。

IDisposable の目的は、多くの手作業を行わなくてもコードがより適切に機能するようにすることです。その力を有利に利用して、人工的な「設計意図」のナンセンスを乗り越えてください。

.NET が最初に登場したとき、決定論的ファイナライズの有用性を Microsoft に納得させるのは非常に困難だったことを覚えています。

于 2009-01-16T22:43:51.247 に答える
15

私の個人的な投票は、イベントからクラスを削除するために Unsubscribe メソッドを持つことです。IDisposable は、管理されていないリソースを決定論的に解放するためのパターンです。この場合、管理されていないリソースを管理していないため、IDisposable を実装しないでください。

IDisposable は、イベント サブスクリプションの管理に使用できますが、使用しない方がよいでしょう。例として、WPF を紹介します。これは、イベントとイベント ハンドラーが豊富なライブラリです。しかし、事実上、WPF には IDisposable を実装するクラスはありません。これは、イベントを別の方法で管理する必要があることを示していると思います。

于 2009-01-16T22:34:12.280 に答える
8

IDisposableイベントからの登録解除にパターンを使用する際に気になることの 1 つは、ファイナライズの問題です。

Dispose()関数 inIDisposableは開発者によって呼び出されることになっていますが、開発者によって呼び出されない場合、GC がこの関数を呼び出すことが理解されています (IDisposable少なくとも標準パターンでは)。ただし、あなたの場合、Dispose他の誰も呼び出さなければ、イベントは残り、強い参照は GC がファイナライザーを呼び出さないようにします。

() が GC によって自動的に呼び出されないという単なる事実Disposeだけで、この場合 IDisposable を使用しないように思えます。おそらく、このタイプのオブジェクトには、GC によって破棄されるために呼び出されるCleanup関数が必要であることを示す、新しいアプリケーション固有のインターフェイスが必要です。

于 2010-10-26T10:16:43.887 に答える
5

GC が自動的に処理できないものはすべて使い捨てだと思います。私の本ではイベント参照がカウントされます。これが私が思いついたヘルパークラスです。

public class DisposableEvent<T> : IDisposable
    {

        EventHandler<EventArgs<T>> Target { get; set; }
        public T Args { get; set; }
        bool fired = false;

        public DisposableEvent(EventHandler<EventArgs<T>> target)
        {
            Target = target;
            Target += new EventHandler<EventArgs<T>>(subscriber);
        }

        public bool Wait(int howLongSeconds)
        {
            DateTime start = DateTime.Now;
            while (!fired && (DateTime.Now - start).TotalSeconds < howLongSeconds)
            {
                Thread.Sleep(100);
            }
            return fired;
        }

        void subscriber(object sender, EventArgs<T> e)
        {
            Args = e.Value;
            fired = true;
        }

        public void Dispose()
        {
            Target -= subscriber;
            Target = null;
        }

    }

このコードを書くことができます:

Class1 class1 = new Class1();
            using (var x = new DisposableEvent<object>(class1.Test))
            {
                if (x.Wait(30))
                {
                    var result = x.Args;
                }
            }

1 つの副作用として、イベントに event キーワードを使用しないでください。これにより、イベントをパラメーターとしてヘルパー コンストラクターに渡すことができなくなりますが、悪影響はないようです。

于 2009-08-05T15:16:04.730 に答える
4

別のオプションは、明示的にサブスクライブを解除する代わりに、弱いデリゲートまたはWPF のweak eventsのようなものを使用することです。

PS [OT] 私は、強力なデリゲートのみを提供するという決定は、.NET プラットフォームの唯一の最もコストのかかる設計ミスだと考えています。

于 2009-01-17T08:49:33.940 に答える
4

ディスポーザブルについて読んだすべてのことから、ディスポーザブルは主に 1 つの問題を解決するために発明されたものであると言えます。それは、管理されていないシステム リソースをタイムリーに解放することです。しかし、私が見つけたすべての例は、管理されていないリソースのトピックに焦点を当てているだけでなく、別の共通のプロパティも持っています: Dispose は、そうでなければ後で自動的に発生するプロセスを高速化するために呼び出されます(GC -> ファイナライザー ->廃棄)

ただし、dispose を呼び出すファイナライザーを追加したとしても、イベントのサブスクライブを解除する dispose メソッドの呼び出しは自動的には行われません。(少なくとも、オブジェクトを所有するイベントが存在する限りではありません。それが呼び出された場合、オブジェクトを所有するイベントも失われるため、サブスクリプション解除のメリットはありません)

したがって、主な違いは、イベント処理オブジェクトが、参照/使用したいサービスから突然参照されるようになるため、収集できないオブジェクトグラフをイベントが何らかの方法で構築することです。突然Dispose を呼び出さなければならなくなりました -自動破棄は不可能です。したがって、Dispose は、Dispose 呼び出しが (汚い理論では ;) - 自動的に (ある時点で) 呼び出されるため、不要なすべての例で見られるものとは別の微妙な意味を持ちます...

ともかく。使い捨てパターンはすでにかなり複雑なものであるため (正しく取得するのが難しいファイナライザーと多くのガイドライン/契約を処理します)、さらに重要なことに、ほとんどの点で、トピックを逆参照するイベントとは何の関係もありません。 「オブジェクトグラフからルートを解除する」/「停止する」/「オフにする」と呼ばれる可能性のあるものにそのメタファーを使用しないだけで、頭の中でそれを分離しやすくなります。

私たちが達成したいのは、(イベントの購読を解除することによって) いくつかの動作を無効化/停止することです。Stop() メソッドを備えた IStoppable のような標準インターフェースがあればいいと思います。

  • オブジェクト (+ すべての独自の停止可能オブジェクト) を、独自に作成していないオブジェクトのイベントから切断する
  • 暗黙的なイベント スタイルの方法で呼び出されないようにします (したがって、停止したと認識される可能性があります)。
  • そのオブジェクトへの従来の参照がなくなるとすぐに収集できます

サブスクリプション解除を行う唯一のインターフェース メソッド「Stop()」を呼び出しましょう。停止したオブジェクトは許容可能な状態ですが、停止しているだけであることがわかります。たぶん、単純なプロパティ「停止」もあればいいでしょう。

IStoppable を継承するインターフェイス "IRestartable" を用意し、さらにメソッド "Restart()" を追加することも理にかなっています後で元に戻すための履歴のモデル オブジェクト。

すべての書き込みの後、ここのどこかで IDisposable の例を見たことを告白しなければなりません: http://msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx しかしとにかく私まですべての詳細と IObservable の元の動機を取得します。これは最良の使用例ではないと思います

  • 繰り返しになりますが、それはかなり複雑なシステムであり、ここには小さな問題しかありません
  • そのまったく新しいシステムの動機の 1 つは、そもそもイベントを取り除くことである可能性があります。これにより、元の質問に関する一種のスタック オーバーフローが発生します。

しかし、彼らは正しい軌道に乗っているようです。とにかく:彼らは私のインターフェース「IStoppable」を使うべきだった;)私は違いがあると強く信じているので

  • Dispose: 「そのメソッドを呼び出す必要があります。そうしないと、GC が遅れた場合に何かリークする可能性があります」 ....

  • 停止: "特定の動作を停止するには、このメソッドを呼び出す必要があります"
于 2011-01-05T01:23:54.563 に答える
3

1つのオプションは、サブスクリプションをまったく解除しないことです。サブスクリプションの意味を変更するだけです。イベントハンドラーを、コンテキストに基づいて何を意味するのかを十分に理解できるようにすることができれば、そもそも購読を解除する必要はありません。

あなたの特定のケースでは、それは良い考えかもしれませんし、そうでないかもしれません-私たちは本当に十分な情報を持っているとは思いません-しかし、それは検討する価値があります。

于 2009-01-16T23:20:32.140 に答える
3

IDisposable はしっかりとリソースに関するものであり、これ以上水を濁らせないようにするのに十分な問題の原因だと思います。

あなた自身のインターフェースでも Unsubscribe メソッドに投票しています。

于 2009-01-16T22:39:03.190 に答える
1

いいえ、IDisposable の意図を妨げているわけではありません。IDisposable は、オブジェクトの使用が終了したときに、そのオブジェクトに関連付けられているすべてのものを事前にクリーンアップできるようにするための汎用的な方法として意図されています。アンマネージド リソースだけである必要はなく、マネージド リソースも含めることができます。そして、イベント サブスクリプションは、もう 1 つの管理対象リソースです。

実際に頻繁に発生する同様のシナリオは、別のマネージド オブジェクトで Dispose() を呼び出せるようにするためだけに、型に IDisposable を実装することです。これも倒錯ではなく、きちんとしたリソース管理です。

于 2013-08-21T17:19:57.413 に答える