デリゲートとインターフェイス-さらに説明がありますか?


23

記事-インターフェイスの代わりにデリゲートを使用するタイミング(C#プログラミングガイド)を読んだ後 、以下の特定のポイントを理解するのに多少の助けが必要です。これらの例や詳細な説明はありますか?

次の場合にデリゲートを使用します。

  • イベンティングデザインパターンが使用されます。
  • 静的メソッドをカプセル化することが望ましいです。
  • 簡単な構成が望まれます。
  • クラスには、メソッドの複数の実装が必要な場合があります。

次の場合にインターフェースを使用します。

  • 呼び出される可能性のある関連メソッドのグループがあります。
  • クラスには、メソッドの実装が1つだけ必要です。

私の質問は、

  1. イベントデザインパターンとはどういう意味ですか?
  2. デリゲートを使用した場合、どのように構成が簡単になりますか?
  3. 呼び出される可能性のある関連メソッドのグループがある場合、インターフェースを使用します-それにはどんな利点がありますか?
  4. クラスがメソッドの実装を1つだけ必要とする場合、インターフェースを使用します。利点の点でそれはどのように正当化されますか?

回答:


11

イベントデザインパターンとはどういう意味ですか?

ほとんどの場合、「イベント」として公開されるC#のコア言語コンストラクトであるオブザーバーパターンの実装を参照します。イベントをリッスンするには、デリゲートをイベントにフックします。Yam Marcovicが指摘したように、これEventHandlerは従来のイベントの基本デリゲートタイプですが、任意のデリゲートタイプを使用できます。

デリゲートを使用した場合、どのように構成が簡単になりますか?

これはおそらく、デリゲートが提供する柔軟性を指しているだけです。特定の動作を簡単に「構成」できます。lambdasの助けを借りて、これを行う構文も非常に簡潔です。次の例を考えてみましょう。

class Bunny
{
    Func<bool> _canHop;

    public Bunny( Func<bool> canHop )
    {
        _canHop = canHop;
    }

    public void Hop()
    {
        if ( _canHop() )  Console.WriteLine( "Hop!" );
    }
}

Bunny captiveBunny = new Bunny( () => IsBunnyReleased );
Bunny lazyBunny = new Bunny( () => !IsLazyDay );
Bunny captiveLazyBunny = new Bunny( () => IsBunnyReleased && !IsLazyDay );

インターフェイスで同様のことを行うには、戦略パターンを使用するか、Bunnyより具体的なバニーを拡張する(抽象的な)基本クラスを使用する必要があります。

呼び出される可能性のある関連メソッドのグループがある場合、インターフェイスを使用します-それにはどのような利点がありますか?

繰り返しますが、バニーを使用して、それがどのように簡単になるかを示します。

interface IAnimal
{
    void Jump();
    void Eat();
    void Poo();
}

class Bunny : IAnimal { ... }
class Chick : IAnimal { ... }

// Using the interface.
IAnimal bunny = new Bunny();
bunny.Jump();  bunny.Eat();  bunny.Poo();
IAnimal chick = new Chick();
chick.Jump();  chick.Eat();  chick.Poo();

// Without the interface.
Action bunnyJump = () => bunny.Jump();
Action bunnyEat = () => bunny.Eat();
Action bunnyPoo = () => bunny.Poo();
bunnyJump(); bunnyEat(); bunnyPoo();
Action chickJump = () => chick.Jump();
Action chickEat = () => chick.Eat();
...

クラスがメソッドの実装を1つだけ必要とする場合、インターフェースを使用します。利点の点でそれはどのように正当化されますか?

このために、バニーの最初の例をもう一度考えてみましょう。実装が1つだけ必要な場合(カスタムコンポジションは不要)、この動作をインターフェイスとして公開できます。ラムダを構築する必要はありません。インターフェイスを使用するだけです。

結論

デリゲートはより多くの柔軟性を提供し、インターフェイスは強力な契約の確立に役立ちます。したがって、「クラスにはメソッドの複数の実装が必要な場合があります」という最後のポイントが見つかりました、最も関連性の高いものです。

デリゲートを使用する追加の理由は、ソースファイルを調整できないクラスの一部のみを公開する場合です。

このようなシナリオの例(最大限の柔軟性、ソースを変更する必要なし)として、2つのデリゲートを渡すだけで、可能なコレクションのバイナリ検索アルゴリズムのこの実装を検討しください。


12

デリゲートは、単一のメソッドシグネチャのインターフェイスのようなものであり、通常のインターフェイスのように明示的に実装する必要はありません。実行時に構築できます。

インターフェースは、何らかの契約を表す単なる言語構造です-「これにより、以下のメソッドとプロパティを利用可能にすることを保証します」。

また、オブザーバー/サブスクライバーパターンのソリューションとしてデリゲートを使用する場合、デリゲートが主に有用であることも完全には同意しません。ただし、これは「javaの冗長性の問題」に対する洗練されたソリューションです。

ご質問:

1&2)

Javaでイベントシステムを作成する場合は、通常、次のようなインターフェイスを使用してイベントを伝播します。

interface KeyboardListener
{
    void KeyDown(int key);
    void KeyUp(int key)
    void KeyPress(int key);
    .... and so on
}

つまり、クラスはこれらすべてのメソッドを明示的に実装する必要があり、単に実装したい場合でもすべてのメソッドにスタブを提供する必要がありますKeyPress(int key)

C#では、これらのイベントはデリゲートのリストとして表され、c#の "event"キーワードによって隠され、各イベントに1つずつ表示されます。これは、クラスにパブリックな「キー」メソッドなどを与えずに、必要なものを簡単にサブスクライブできることを意味します。

ポイント3〜5に対して+1。

さらに:

デリゲートは、たとえば、リストを取得し、各要素を同じ量であるが何らかの方法で異なる新しいリストに投影する「マップ」機能を提供する場合に非常に便利です。基本的に、IEnumerable.Select(...)。

IEnumerable.Selectはを受け取りますFunc<TSource, TDest>。これは、TSource要素を受け取り、その要素をTDest要素に変換する関数をラップするデリゲートです。

Javaでは、これはインターフェイスを使用して実装する必要があります。多くの場合、このようなインターフェイスを実装する自然な場所はありません。何らかの方法で変換したいリストがクラスに含まれている場合、特に異なる方法で変換する必要がある2つの異なるリストがある可能性があるため、インターフェイス "ListTransformer"を実装することはあまり自然ではありません。

もちろん、(Javaで)同様の概念である匿名クラスを使用できます。


実際に彼の質問に答えるためにあなたのポストを編集を検討
ヤムMarcovic

@YamMarcovicあなたは正しいです、私は彼の質問に直接答えるのではなく、少し自由形式でとりとめました。それでも、私はそれが関係するメカニズムについて少し説明すると思う。また、私が質問に答えたとき、彼の質問は少し形が整っていませんでした。:p
最大

当然。私にも同じことが起こり、それ自体に価値があります。だから私それを提案しただけで、文句は言わなかった。:)
ヤムマルコビッチ

3

1)イベントパターン、古典はObserverパターンです。Microsoftのリンクは MicrosoftトークObserverです。

リンクした記事はあまりよく書かれておらず、必要以上に複雑になっています。静的ビジネスは常識であり、静的メンバーを使用してインターフェイスを定義することはできません。したがって、静的メソッドで多態的な動作が必要な場合は、デリゲートを使用します。

上記のリンクでは、デリゲートと、構成に適している理由について説明しているため、クエリに役立ちます。


3

まず、良い質問です。「ベストプラクティス」を盲目的に受け入れるのではなく、ユーティリティに焦点を当てていることを称賛します。そのために+1。

以前にそのガイドを読んだことがあります。それについて何か覚えておく必要があります-これは単なるガイドであり、主にプログラミングの方法は知っているが、C#のやり方をよく知らないC#の初心者向けです。ルールのページというよりも、すでに通常行われていることを説明するページです。そして、彼らはすでにこの方法でどこでも行われているので、一貫性を保つことは良い考えかもしれません。

あなたの質問に答えて要点を説明します。

まず第一に、インターフェイスが何であるかを既に知っていると思います。デリゲートに関しては、メソッドへの型付きポインタと、thisそのメソッドの引数を表すオブジェクトへのオプションのポインタを含む構造であると言えば十分です。静的メソッドの場合、後者のポインターはnullです。
マルチキャストデリゲートもあります。これは、デリゲートに似ていますが、これらの構造のいくつかが割り当てられている場合があります(マルチキャストデリゲートでInvokeを1回呼び出すと、割り当てられた呼び出しリスト内のすべてのメソッドが呼び出されます)。

イベントデザインパターンとはどういう意味ですか?

それらは、C#でイベントを使用することを意味します(これには、この非常に便利なパターンを巧みに実装するための特別なキーワードがあります)。C#のイベントは、マルチキャストデリゲートによって促進されます。

この例のように、イベントを定義する場合:

class MyClass {
  // Note: EventHandler is just a multicast delegate,
  // that returns void and accepts (object sender, EventArgs e)!
  public event EventHandler MyEvent;

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

コンパイラは実際にこれを次のコードに変換します。

class MyClass {
  private EventHandler MyEvent = null;

  public void add_MyEvent(EventHandler value) {
    MyEvent += value;
  }

  public void remove_MyEvent(EventHandler value) {
    MyEvent -= value;
  }

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

次に、イベントをサブスクライブします

MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;

コンパイルする

MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));

C#(または.NET全般)のイベントです。

デリゲートを使用した場合、どのように構成が簡単になりますか?

これは簡単に実証できます:

渡される一連のアクションに依存するクラスがあるとします。これらのアクションをインターフェースにカプセル化できます。

interface RequiredMethods {
  void DoX();
  int DoY();
};

そして、アクションをクラスに渡したい人は、最初にそのインターフェースを実装する必要があります。または、次のクラスに依存することにより、彼らの生活を楽にすることができます。

sealed class RequiredMethods {
  public Action DoX;
  public Func<int> DoY();
}

このように、呼び出し側はRequiredMethodsのインスタンスを作成し、実行時にメソッドをデリゲートにバインドするだけです。これ通常簡単です。

この方法は、適切な状況下で非常に有益です。それについて考えてください-本当にあなたが気にかけているのは、あなたに渡された実装を持っているだけなのに、なぜインターフェイスに依存するのですか?

関連するメソッドのグループがある場合にインターフェースを使用する利点

インターフェイスは通常、明示的なコンパイル時の実装を必要とするため、インターフェイスを使用することは有益です。これは、新しいクラスを作成することを意味します。
また、単一のパッケージに関連するメソッドのグループがある場合、そのパッケージをコードの他の部分で再利用できると便利です。したがって、一連のデリゲートを構築するのではなく、クラスを単純にインスタンス化できれば、簡単です。

クラスが1つの実装のみを必要とする場合にインターフェースを使用する利点

前述のように、インターフェイスはコンパイル時に実装されます。つまり、デリゲート(それ自体が間接的なレベルです)を呼び出すよりも効率的です。

「1つの実装」とは、明確に定義された単一の場所に存在する実装を意味する場合があります。
それ以外の場合、実装はプログラムのどこからでも発生する可能性があり、たまたまメソッドシグネチャ準拠しています。メソッドは、特定のインターフェイスを明示的に実装するクラスに属するのではなく、予想される署名に準拠するだけでよいため、柔軟性が高まります。しかし、その柔軟性は犠牲になる可能性があり、実際にはリスコフの置換原則を破ります。なぜなら、ほとんどの場合、事故の可能性を最小限に抑えるため、明示性が必要だからです。静的入力と同じです。

この用語は、ここでマルチキャストデリゲートを指す場合もあります。インターフェイスによって宣言されたメソッドは、実装クラスで1回しか実装できません。ただし、デリゲートは複数のメソッドを蓄積することができ、それらは順次呼び出されます。

全体として、ガイドは十分な情報を提供しておらず、単にルールブックではなくガイドとして機能しているように見えます。いくつかのアドバイスは実際には少し矛盾するように聞こえるかもしれません。何を適用するのが適切かを決めるのはあなた次第です。このガイドは、一般的な道筋のみを示しているようです。

あなたの質問があなたの満足に答えられることを望みます。そして再び、質問への称賛。


2

.NETの記憶がまだ残っている場合、デリゲートは基本的に関数ptrまたはファンクターです。関数呼び出しに間接層を追加して、呼び出しコードを変更せずに関数を置換できるようにします。これは、インターフェイスが複数の機能をまとめてパッケージ化し、実装者がそれらを一緒に実装する必要があることを除いて、インターフェイスが行うことと同じです。

大まかに言えば、イベントパターンとは、どこかからのイベント(Windowsメッセージなど)に何かが応答するパターンです。通常、一連のイベントは無制限であり、任意の順序で発生する可能性があり、必ずしも相互に関連しているわけではありません。デリゲートは、多数の無関係な関数を含む可能性のある一連の実装オブジェクトへの参照を必要とせずに各イベントが単一の関数を呼び出すことができるという点で、これに対してうまく機能します。さらに(これは私の.NETメモリが曖昧な場所です)、複数のデリゲートをイベントにアタッチできると思います。

合成は、この用語にはあまり馴染みがありませんが、基本的には、1つのオブジェクトを設計して、複数のサブパーツ、または作業が継承される集約された子を持つようにします。デリゲートを使用すると、インターフェイスが過剰になったり、カップリングが過剰になったり、それに伴う剛性と脆弱性が生じたりする可能性のある、よりアドホックな方法で子供を混ぜ合わせることができます。

関連メソッドのインターフェイスの利点は、メソッドが実装オブジェクトの状態を共有できることです。デリゲート関数は、状態をそれほどきれいに共有することはできません。

クラスが単一の実装を必要とする場合、コレクション全体を実装するクラス内では1つの実装しか実行できず、実装クラス(状態、カプセル化など)の利点が得られるため、インターフェイスがより適しています。実行時の状態が原因で実装が変更される可能性がある場合、デリゲートは他のメソッドに影響を与えずに他の実装用に交換できるため、動作が向上します。たとえば、それぞれに2つの実装が可能な3つのデリゲートがある場合、状態のすべての可能な組み合わせを考慮するために、3メソッドインターフェイスを実装する8つの異なるクラスが必要になります。


1

「イベント」デザインパターン(より良いオブザーバーパターン)を使用すると、同じ署名の複数のメソッドをデリゲートにアタッチできます。インターフェースでそれを実際に行うことはできません。

ただし、デリゲートにとっては、インターフェイスよりも構成が簡単だとはまったく確信していません。それは非常に奇妙な声明です。匿名メソッドをデリゲートにアタッチできるので、彼が意味するのかしら。


1

私が提供できる最大の明確化:

  1. デリゲートは、関数シグネチャを定義します-一致する関数が受け入れるパラメーターと、返されるもの。
  2. インターフェイスは、関数、イベント、プロパティ、およびフィールドのセット全体を処理します。

そう:

  1. 同じシグネチャを持ついくつかの関数を一般化したい場合-デリゲートを使用します。
  2. クラスの動作または品質を一般化する場合-インターフェイスを使用します。

1

イベントに関するあなたの質問はすでにうまくカバーされています。また、インターフェイス複数のメソッド定義できますが(実際には必要ありません)、関数型は個々の関数にのみ制約を設定します。

ただし、本当の違いは次のとおりです。

  • 関数の値は、構造的なサブタイピング(つまり、要求された構造への暗黙的な互換性)によって関数タイプと照合されます。つまり、関数値に関数タイプと互換性のあるシグネチャがある場合、それはそのタイプの有効な値です。
  • インスタンスは、名目上のサブタイピング(つまり、名前の明示的な使用)によってインターフェイスに一致します。つまり、インスタンスが特定のインターフェイスを明示的に実装するクラスのメンバーである場合、インスタンスはインターフェイスの有効な値になります。ただし、インターフェイスに要求されるすべてのメンバーがオブジェクトにあり、すべてのメンバーに互換性のあるシグネチャがあり、インターフェイスを明示的に実装していない場合、インターフェイスの有効な値ではありません。

確かに、これはどういう意味ですか?

この例を見てみましょう(私のC#はあまり良くないので、コードはhaXeにあります):

class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:T->Bool):Collection<T> { 
         //build a new collection with all elements e, such that predicate(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

現在、filter-methodを使用すると、コレクションの内部構成を認識せずに、コレクションに与えられたロジックに依存しない小さなロジックを簡単に渡すことができます。すばらしいです。1つの問題を除いて、
コレクションロジックに依存します。コレクションは本質的に、渡された関数が条件に対して値をテストし、テストの成功を返すように設計されているという仮定を立てます。引数として1つの値を取り、ブール値を返すすべての関数が実際には単なる述語ではないことに注意してください。たとえば、コレクションのremoveメソッドはそのような関数です。
を呼び出したとしc.filter(c.remove)ます。結果は、cwhileのすべての要素を持つコレクションになりますcそれ自体は空になります。当然、c不変であると予想されるため、これは残念です。

この例は非常に構成されています。しかし重要な問題は、c.filter引数として何らかの関数値を使用して呼び出すコードは、その引数が適切かどうかを知る方法がないということです(つまり、最終的に不変式を保存します)。関数の値を作成したコードは、それが述語として解釈されることを知っている場合と知らない場合があります。

それでは、物事を変えましょう:

interface Predicate<T> {
    function test(value:T):Bool;
}
class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:Predicate<T>):Collection<T> { 
         //build a new collection with all elements e, such that predicate.test(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

変化したこと?変更されたのは、現在与えられてfilterいる値がなんであれ、述語であることの契約に明示的に署名したことです。もちろん、悪意のあるまたは非常に愚かなプログラマーは、副作用がなく、したがって述語ではないインターフェースの実装を作成します。
しかし、もはや起こり得ないことは、誰かがデータ/コードの論理ユニットを結び付け、その外部構造のために誤って述語として解釈されることです。

それで、ほんの少しの言葉で上記のことを言い換えると:

  • インターフェースは、名義型を使用し、それによって明示的な関係を使用することを意味します
  • デリゲートは、構造型付けを使用することにより、暗黙的な関係を意味します

明示的な関係の利点は、それらを確実に特定できることです。欠点は、明示性のオーバーヘッドが必要になることです。逆に、暗黙的な関係(この場合、1つの関数の署名が目的の署名と一致する)の欠点は、この方法で物事を使用できることを100%確信できないことです。利点は、すべてのオーバーヘッドなしで関係を確立できることです。それらの構造がそれを可能にするので、あなたはただ素早く物を一緒に投げることができます。それが簡単な合成の意味です。
それは少しLEGOのようなものです:あなたがすることができ、単に外部構造がそれを可能にするという理由だけで、LEGO海賊船にスターウォーズレゴフィギュアを差し込みます。今、あなたはそれがひどく間違っていると感じるかもしれません、またはそれはちょうどあなたが望むものであるかもしれません。誰もあなたを止めるつもりはありません。


1
「インターフェイスを明示的に実装する」(「公称サブタイピング」の下)は、インターフェイスメンバの明示的または暗黙的な実装とは異なります。あなたが言うように、C#では、クラスは実装するインターフェイスを常に明示的に宣言する必要があります。ただし、インターフェイスメンバは、明示的に(たとえばint IList.Count { get { ... } })または暗黙的に(public int Count { get { ... } })実装できます。この区別はこの議論には関係ありませんが、読者を混乱させないために言及するに値します。
phoog

@phoog:はい、修正をありがとう。最初のものが可能かどうかさえ知りませんでした。しかし、はい、言語が実際にインターフェース実装を実施する方法は大きく異なります。たとえば、Objective-Cでは、実装されていない場合にのみ警告が表示されます。
back2dos

1
  1. イベントデザインパターンは、パブリッシュおよびサブスクライブメカニズムを含む構造を意味します。イベントはソースによって公開され、すべてのサブスクライバーは公開されたアイテムの独自のコピーを取得し、独自のアクションを担当します。これは、パブリッシャーがサブスクライバーがそこにいることを知る必要すらないため、疎結合メカニズムです。サブスクライバーを持つことはもちろんのこと、必須ではありません(ない場合もあります)
  2. デリゲートがインターフェイスと比較して使用される場合、構成は「簡単」です。インターフェイスは、クラスインスタンスを、「コンストラクトコンストラクトインスタンスが「ハンドラーを持っている」ように見える「種類のハンドラー」オブジェクトであると定義します。
  3. 以下のコメントから、投稿を改善する必要があることがわかりました。参照用にこれを参照してください:http : //bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate

1
3.デリゲートがより多くのキャストを必要とするのはなぜですか、そしてなぜより密な結合が利点なのですか 4.デリゲートを使用するためにイベントを使用する必要はありません。デリゲートを使用すると、メソッドを保持するのと同じように、戻り値の型と引数の型がわかります。また、インターフェイスで宣言されたメソッドは、デリゲートにアタッチされたメソッドよりもエラーを処理しやすくするのはなぜですか?
ヤムマルコビッチ

デリゲートの仕様だけの場合、あなたは正しいです。ただし、イベント処理の場合(少なくとも私の参照が関係するものです)、カスタムタイプのコンテナとして機能するために、常に何らかの種類のeventArgsを継承する必要があるため、値を安全にテストする代わりに、常に値を処理する前に型をラップ解除する必要があります。
カルロカイプ

インターフェイスで定義されたメソッドがエラーを処理しやすくすると思う理由については、コンパイラがそのメソッドからスローされた例外タイプをチェックできるようにすることです。私はそれが説得力のある証拠ではないことを知っていますが、おそらく好みとして述べられるべきですか?
カルロカイプ

1
まず、イベント対インターフェイスではなく、デリゲート対インターフェイスについて話していました。第二に、イベント自体をEventHandlerデリゲートに基づいて作成することは単なる慣例であり、決して必須ではありません。繰り返しますが、ここでのイベントについて話すことは、ポイントを逃します。2番目のコメントに関しては、C#では例外はチェックされません。あなたは、Java(たまたまデリゲートさえない)と混同されています。
ヤムマルコビッチ

重要な違いを説明するこのテーマに関するスレッドを見つけました:bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate
Carlo Kuip
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.