web-dev-qa-db-ja.com

C#:イベントまたはオブザーバーインターフェイス?長所短所?

私は次のものを持っています(簡略化):

interface IFindFilesObserver
{
    void OnFoundFile(FileInfo fileInfo);
    void OnFoundDirectory(DirectoryInfo directoryInfo);
}

class FindFiles
{
    IFindFilesObserver _observer;

    // ...
}

...そして私は対立しています。これは基本的にはC++で記述したものですが、C#にはイベントがあります。イベントを使用するようにコードを変更する必要がありますか、それともそのままにする必要がありますか?

従来のオブザーバーインターフェイスを超えるイベントの利点または欠点は何ですか?

58
Roger Lipscombe

イベントがコールバックインターフェースであり、インターフェースにメソッドが1つしかない場合を考えます。

必要なフックイベントのみ
イベントでは、処理したいイベントのハンドラーを実装するだけで済みます。オブザーバーインターフェイスパターンでは、実際に処理する必要のない通知タイプのメソッド本体の実装を含め、インターフェイス全体ですべてのメソッドを実装する必要があります。あなたの例では、これらのイベントの1つだけに関心がある場合でも、常にOnFoundDirectoryとOnFoundFileを実装する必要があります。

メンテナンスが少ない
イベントのもう1つの良い点は、特定のクラスに新しいイベントを追加して、それを発生させることができ、既存のオブザーバーをすべて変更する必要がないことです。一方、新しいメソッドをインターフェイスに追加する場合は、そのインターフェイスを既に実装しているすべてのクラスを回避し、それらすべてに新しいメソッドを実装する必要があります。ただしイベントの場合、追加する新しいイベントに応じて実際に何かを実行したい既存のクラスを変更するだけで済みます。

パターンは言語に組み込まれているので、誰もがそれを使用する方法を知っています
イベントは慣用的であり、イベントを見ると、その使用方法がわかります。オブザーバーインターフェイスを使用すると、通知を受信して​​オブザーバーをフックするためのさまざまな登録方法を実装することがよくあります。ただし、イベントを登録して使用する方法(+ =演算子を使用)を習得したら、残りはすべてです。同じ。

インターフェースの長所
インターフェイスのプロはあまりいません。彼らは誰かにインターフェースのすべてのメソッドを実装するように強制していると思います。しかし、実際に誰かにこれらすべてのメソッドを正しく実装するよう強制することはできないので、これには多くの価値があるとは思いません。

構文
一部の人々は、各イベントのデリゲートタイプを宣言する必要がある方法を好みません。また、以下の.Netフレームワークの標準イベントハンドラーには、これらのパラメーターがあります(オブジェクト送信者、EventArgs引数)。送信者は特定のタイプを指定しないため、使用する場合はダウンキャストする必要があります。静的型システムの保護を失っているので、これは実際には大抵問題ありません。ただし、独自のイベントを実装し、これに関する.Netフレームワークの規則に従っていない場合は、正しいタイプを使用できるため、ダウンキャストの可能性はありません。

66
Scott Langham

うーん、イベントを使用してObserverパターンを実装できます。実際、イベントの使用は、オブザーバーパターンimhoの別の実装と見なすことができます。

29

インターフェイスソリューションの長所:

  • メソッドを追加する場合、既存のオブザーバーはそれらのメソッドを実装する必要があります。つまり、既存のオブザーバーを新しい機能に結びつけるのを忘れる可能性が少なくなります。もちろん、それらを空のメソッドとして実装することもできます。これは、特定の「イベント」に応答して何もしないという贅沢があることを意味します。しかし、簡単に忘れることはありません。
  • 明示的な実装を使用すると、逆にコンパイラエラーも発生します。既存のインターフェースを削除または変更すると、それらを実装するオブザーバーはコンパイルを停止します。

短所:

  • オブザーバーインターフェイスを変更すると、ソリューション全体に変更が適用される可能性があり、別の計画が必要になる場合があるため、計画を立てる必要があります。単純なイベントはオプションであるため、他のコードがイベントに反応する必要がない限り、他のコードを変更する必要はほとんどありません。

イベントのいくつかのさらなる利点。

  • 無料で適切なマルチキャスト動作が得られます。
  • イベントに応答してイベントのサブスクライバーを変更した場合、動作は明確に定義されています
  • 簡単かつ一貫して内省(反映)できる
  • イベントのツールチェーンサポート(単に.netのイディオムであるため)
  • あなたはそれが提供する非同期APIを使用するオプションを取得します

これらすべて(ツールチェーンを除く)を自分で実現できますが、それは驚くほど困難です。例:List <>のようなメンバー変数を使用してオブザーバーのリストを格納する場合。 foreachを使用して反復する場合、OnFoo()メソッドコールバックの1つ内でサブスクライバーを追加または削除しようとすると、それを適切に処理するためのコードを記述しない限り、例外がトリガーされます。

7
ShuggyCoUk
  • たとえば、FACADEパターンを使用したり、作業を他のクラスに委任したりすると、イベントはオブジェクトのチェーン全体に伝わりにくくなります。
  • オブジェクトのガベージコレクションを許可するには、イベントのサブスクライブを解除する際に十分な注意が必要です。
  • イベントは、単純な関数呼び出しよりも2倍遅く、すべてのレイズでnullチェックを実行すると3倍遅くなり、nullチェックと呼び出しの前にイベントデリゲートをコピーしてスレッドセーフにします。

  • また、 4.0の新機能についてのMSDNを読んでください)IObserver<T>インターフェース。

この例を考えてみましょう:

using System;

namespace Example
{
    //Observer
    public class SomeFacade
    {
        public void DoSomeWork(IObserver notificationObject)
        {
            Worker worker = new Worker(notificationObject);
            worker.DoWork();
        }
    }
    public class Worker
    {
        private readonly IObserver _notificationObject;
        public Worker(IObserver notificationObject)
        {
            _notificationObject = notificationObject;
        }
        public void DoWork()
        {
            //...
            _notificationObject.Progress(100);
            _notificationObject.Done();
        }
    }
    public interface IObserver
    {
        void Done();
        void Progress(int amount);
    }

    //Events
    public class SomeFacadeWithEvents
    {
        public event Action Done;
        public event Action<int> Progress;

        private void RaiseDone()
        {
            if (Done != null) Done();
        }
        private void RaiseProgress(int amount)
        {
            if (Progress != null) Progress(amount);
        }

        public void DoSomeWork()
        {
            WorkerWithEvents worker = new WorkerWithEvents();
            worker.Done += RaiseDone;
            worker.Progress += RaiseProgress;
            worker.DoWork();
            //Also we neede to unsubscribe...
            worker.Done -= RaiseDone;
            worker.Progress -= RaiseProgress;
        }
    }
    public class WorkerWithEvents
    {
        public event Action Done;
        public event Action<int> Progress;

        public void DoWork()
        {
            //...
            Progress(100);
            Done();
        }
    }
}
7
Alex Burtsev

長所は、イベントはより「ドットネット」であるということです。フォームにドロップできる非ビジュアルコンポーネントを設計している場合は、デザイナーを使用してそれらを接続できます。

短所は、イベントが単一のイベントを示すだけであることです。オブザーバーに通知する「もの」ごとに個別のイベントが必要です。これは実際にはそれほど実際的な影響はありませんが、観測された各オブジェクトは、すべての観測者の参照をすべてのイベントに対して保持する必要があり、観測されたオブジェクトが多数ある場合にメモリを膨らませる必要があります(異なる方法で作成した理由の1つ) WPFでのオブザーバー/オブザーバブル関係の管理)。

あなたの場合、それは大きな違いはないと主張します。通常、オブザーバーがこれらすべてのイベントに関心がある場合は、個別のイベントではなく、オブザーバーインターフェイスを使用します。

3
U62

Javaは匿名インターフェースの言語サポートを備えているため、コールバックインターフェースはJavaで使用するものです。

C#は匿名のデリゲート(ラムダ)をサポートしているため、イベントはC#で使用するものです。

2

決定する最良の方法はこれです。どちらが状況により適していますか。それはばかげているか役に立たない答えのように聞こえるかもしれませんが、私はあなたがどちらか一方を「適切な」ソリューションと見なすべきではないと思います。

私たちはあなたに100のヒントを投げることができます。イベントは、オブザーバーが任意のイベントをリッスンすることが期待される場合に最適です。インターフェイスは、オブザーバーが特定のイベントセットすべてにリストされることが期待される場合に最適です。イベントは、GUIアプリを扱うときに最適です。インターフェースは、より少ないメモリを消費します(複数のイベントに対する単一のポインター)。ヤッダヤッダヤッダ賛否両論の箇条書きのリストは、検討すべきことですが、決定的な答えではありません。実際に必要なのは、実際のアプリケーションで両方を試してみて、それらの感触を良くすることです。次に、状況により適したものを選択できます。フォームを学ぶ。

単一の定義に関する質問を使用する必要がある場合は、状況をよりよく説明しているものを自問してください。使用または無視される可能性のある緩やかに関連するイベントのセット、またはすべてが一般に処理する必要がある密接に関連するイベントのセット一人の観察者。しかし、それでは、イベントモデルとインターフェイスモデルについて説明しているだけなので、1に戻ります。どちらが状況により適していますか?

2
snarf

インターフェースの利点は、デコレーターをより簡単に適用できることです。標準的な例:

subject.RegisterObserver(new LoggingObserver(myRealObserver));

に比べ:

subject.AnEvent += (sender, args) => { LogTheEvent(); realEventHandler(sender, args); };

(私はデコレータパターンの大ファンです)。

2
Andreas

次の理由で、イベントベースのソリューションを好みます

  • エントリーコストを削減します。本格的なインターフェースを実装するよりも、「+ = new EventHandler」と言う方がはるかに簡単です。
  • メンテナンスコストを削減します。クラスに新しいイベントを追加する場合は、これで完了です。インターフェイスに新しいイベントを追加する場合は、コードベースのすべてのコンシューマーを更新する必要があります。または、時間の経過とともに「IRandomEvent2またはIRandomEvent5を実装しますか?」という煩わしさを感じるまったく新しいインターフェースを定義します。
  • イベントにより、ハンドラーを非クラスベースにすることができます(つまり、静的メソッドのどこか)。すべてのイベントハンドラーをインスタンスメンバーに強制する機能上の理由はありません
  • 一連のイベントをインターフェイスにグループ化することは、イベントの使用方法についての仮定を行うことです(そして、それは単なる仮定です)。
  • インターフェースは、生のイベントに比べて本当の利点はありません。
1
JaredPar

NetDataContractSerializer または protobuf などの参照を保持する方法でオブジェクトをシリアル化する必要がある場合、イベントはシリアル化境界を越えることができません。オブザーバーパターンはオブジェクト参照だけに依存しているため、このタイプのシリアル化で問題がなければ問題なく機能します。

例Webサービスに渡す必要がある、双方向に相互にリンクする多数のビジネスオブジェクトがあります。

0
jpierson