web-dev-qa-db-ja.com

デリゲートとインターフェイス-利用できる説明はありますか?

記事を読んだ後 インターフェイスの代わりにデリゲートを使用する場合(C#プログラミングガイド) 、(私にとって)あまり明確ではないことがわかった以下のポイントを理解するために助けが必要です。これらについて利用可能な例や詳細な説明はありますか?

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

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

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

  • 呼び出すことができる関連メソッドのグループがあります。
  • クラスはメソッドの1つの実装のみを必要とします。

私の質問は、

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

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

それらはおそらくC#のコア言語構造である observer pattern の実装を参照し、 ' events 'として公開されます。イベントをリッスンするには、デリゲートをイベントにフックします。 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つだけ必要な場合-カスタム構成が不要な場合は、この動作をインターフェイスとして公開できます。ラムダを構築する必要はありません。インターフェイスを使用するだけです。

結論

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

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

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

11
Steven Jeuris

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

インターフェースは、いくつかの契約を表す言語構造にすぎません-「これにより、以下のメソッドとプロパティを使用可能にすることを保証します」。

また、デリゲートがオブザーバー/サブスクライバーパターンのソリューションとして使用される場合、デリゲートが主に役立つことに完全に同意しません。ただし、これは「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つです。これは、公開の「Key」メソッドなどでクラスに負担をかけることなく、必要なものを簡単にサブスクライブできることを意味します。

ポイント3-5の+1。

追加:

デリゲートは、たとえば「マップ」関数を提供する場合に非常に役立ちます。これは、リストを取得し、各要素を同じリストの要素に、いくつかの点で異なる新しいリストに射影します。基本的に、IEnumerable.Select(...)です。

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

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

もちろん、類似した概念(Javaの場合)である匿名クラスを使用することもできます。

12
Max

まず、良い質問です。 「ベストプラクティス」を盲目的に受け入れるのではなく、実用性に重点を置いていることに感心します。 +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つの実装」とは、明確に定義された単一の場所に存在する実装を意味する場合があります。
そうでない場合、実装は、がたまたま発生するメソッドシグネチャに準拠するプログラム内のどこからでも発生する可能性があります。メソッドは、特定のインターフェースを明示的に実装するクラスに属するのではなく、予期されるシグネチャに準拠する必要があるだけなので、柔軟性が高まります。しかし、その柔軟性は犠牲になる可能性があり、実際には Liskov代入の原則 に違反します。なぜなら、事故の可能性を最小限に抑えるため、ほとんどの場合、明示性が必要だからです。静的型付けと同じです。

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

したがって、全体として、ガイドは十分な情報がなく、ルールブックではなくガイドとして機能しているように見えます。いくつかのアドバイスは実際には少し矛盾しているように聞こえるかもしれません。何を適用するのが適切かを判断するのはあなた次第です。ガイドは私たちに一般的な道を与えるだけのようです。

あなたの質問があなたの満足に答えられたことを願っています。繰り返しになりますが、質問に対する称賛。

3
Yam Marcovic

1)イベントパターン、よくあるのはObserverパターンです。ここにMicrosoftの良いリンクがあります MicrosoftトークObserver

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

上記のリンクは、デリゲートと、なぜ構成に適しているかについて説明しているため、クエリに役立つ場合があります。

3
Ian

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

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

合成は、私はこの用語にはあまり詳しくありませんが、基本的には、1つのオブジェクトを複数のサブパート、つまり作業が渡される集約された子を持つように設計することです。デリゲートを使用すると、インターフェースが過剰になりすぎたり、結合が強すぎたり、剛性や脆弱性が生じたりする可能性がある場合に、子供をより臨機応変に組み合わせることができます。

関連メソッドのインターフェースの利点は、メソッドが実装オブジェクトの状態を共有できることです。デリゲート関数は、状態を明確に共有したり、状態を含めたりすることはできません。

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

2
kylben

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

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

そう:

  1. 同じシグネチャを使用して一部の関数を一般化する場合は、デリゲートを使用します。
  2. クラスの動作や品質を一般化したい場合は、インターフェースを使用してください。
1
John Fisher
  1. イベントデザインパターンは、パブリッシュおよびサブスクライブメカニズムを含む構造を意味します。イベントはソースによって公開され、すべてのサブスクライバーは公開されたアイテムの独自のコピーを取得し、独自のアクションを担当します。パブリッシャーはサブスクライバーの存在を知る必要さえないため、これは疎結合メカニズムです。サブスクライバーを持つことは言うまでもなく必須ではありません(なしでもかまいません)
  2. インターフェイスと比較してデリゲートが使用されている場合、構成は「簡単」です。インターフェイスは、クラスインスタンスを「ハンドラーの一種」オブジェクトとして定義します。コンポジションコンストラクトインスタンスは「ハンドラーを持っている」ように見えるため、デリゲートを使用することでインスタンスの外観が制限されなくなり、柔軟性が高まります。
  3. 以下のコメントから、投稿を改善する必要があることがわかりました。これを参照してください: http://bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate
1
Carlo Kuip

「イベント」デザインパターン(オブザーバーパターンとして知られる)を使用すると、同じシグネチャの複数のメソッドをデリゲートにアタッチできます。あなたは本当にそれをインターフェースで行うことはできません。

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

1
pdr

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

ただし、実際の違いは次のとおりです。

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

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

この例を見てみましょう(私の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メソッドを使用すると、コレクションのロジックに依存しない、コレクションの内部編成を認識しない、小さなロジックのみを簡単に渡すことができます。すごい。 1つの問題を除いて:
コレクションdoesはロジックによって異なります。コレクションは本質的に、渡された関数が条件に対して値をテストし、テストの成功を返すように設計されていることを前提としています。 1つの値を引数として取り、ブール値を返すすべての関数が単なる述語であるとは限らないことに注意してください。たとえば、コレクションのremoveメソッドはそのような関数です。
c.filter(c.remove)を呼び出したとします。結果は、c自体が空になる一方で、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に与えられている値がexplicitlyであり、述語であるという契約に署名したことです。もちろん、悪意のあるプログラマーや非常に愚かなプログラマーは、副作用のないインターフェースを実装しているため、述部ではありません。
しかし、もはや起こり得ないことは、誰かがデータ/コードの論理ユニットを拘束し、その外部構造のために誤って述語として解釈されることです。

したがって、上記の内容を簡単に説明すると、次のようになります。

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

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

1
back2dos