666

C#で暗黙的および明示的にインターフェイスを実装する際の違いは何ですか?

いつ暗黙的に使用する必要があり、いつ明示的に使用する必要がありますか?

どちらか一方に賛否両論はありますか?


Microsoftの公式ガイドライン(初版のフレームワーク設計ガイドラインから)では、コードに予期しない動作が発生するため、明示的な実装を使用することはお勧めしません。

このガイドラインは、IoC以前の時代、つまりインターフェースとして物事を渡さない場合に非常に有効だと思います。

誰かがその側面にも触れることができますか?

4

12 に答える 12

514

暗黙的とは、クラスのメンバーを介してインターフェースを定義する場合です。明示的とは、インターフェイスのクラス内でメソッドを定義する場合です。紛らわしいように聞こえますが、これが私が意味することです:IList.CopyTo暗黙的に次のように実装されます:

public void CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

そして明示的に:

void ICollection.CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

違いは、暗黙的な実装では、インターフェイスをそのクラスとして、およびインターフェイス自体としてキャストすることにより、作成したクラスを介してインターフェイスにアクセスできることです。明示的な実装では、インターフェイス自体としてキャストするだけでインターフェイスにアクセスできます。

MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.

私は主に実装をクリーンに保つため、または2つの実装が必要な場合にexplicitを使用します。とにかく、私はめったにそれを使いません。

他の人が投稿する明示的なものを使用する/使用しない理由は他にもあると思います。

それぞれの背後にある優れた理由については、このスレッドの次の投稿を参照してください。

于 2008-09-27T11:07:40.117 に答える
207

暗黙の定義は、インターフェイスが要求するメソッド/プロパティなどをパブリックメソッドとしてクラスに直接追加することです。

明示的な定義により、基本的な実装ではなく、インターフェイスを直接操作している場合にのみメンバーが公開されます。ほとんどの場合、これが推奨されます。

  1. インターフェイスを直接操作することで、コードを認識せず、基盤となる実装に結合する必要がありません。
  2. たとえば、コードにパブリックプロパティNameがすでに含まれていて、Nameプロパティも持つインターフェイスを実装する場合は、明示的に実行すると2つが分離されます。彼らが同じことをしていても、Nameプロパティへの明示的な呼び出しを委任します。通常のクラスでのNameの動作と、インターフェイスプロパティであるNameの動作を後で変更することをお勧めします。
  3. インターフェイスを暗黙的に実装すると、クラスはインターフェイスのクライアントにのみ関連する可能性のある新しい動作を公開するようになります。これは、クラスを十分に簡潔に保っていないことを意味します(私の意見)。
于 2008-09-27T11:09:57.910 に答える
73

すでに提供されている優れた回答に加えて、コンパイラが何が必要かを理解できるようにするために、明示的な実装が必要な場合があります。IEnumerable<T>かなり頻繁に出てくる可能性が高い代表的な例として、を見てみましょう。

次に例を示します。

public abstract class StringList : IEnumerable<string>
{
    private string[] _list = new string[] {"foo", "bar", "baz"};

    // ...

    #region IEnumerable<string> Members
    public IEnumerator<string> GetEnumerator()
    {
        foreach (string s in _list)
        { yield return s; }
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion
}

ここでは、IEnumerable<string>implementsを実装しているIEnumerableため、実装も必要です。ただし、ジェネリック バージョンと通常バージョンの両方で、同じメソッド シグネチャを持つ関数が実装されています(C# は、この戻り値の型を無視します)。これは完全に合法で問題ありません。コンパイラはどちらを使用するかをどのように解決しますか? せいぜい 1 つの暗黙的な定義のみを持つように強制し、必要なものは何でも解決できます。

すなわち。

StringList sl = new StringList();

// uses the implicit definition.
IEnumerator<string> enumerableString = sl.GetEnumerator();
// same as above, only a little more explicit.
IEnumerator<string> enumerableString2 = ((IEnumerable<string>)sl).GetEnumerator();
// returns the same as above, but via the explicit definition
IEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator();

PS: IEnumerable の明示的な定義における小さな間接化が機能するのは、関数内でコンパイラが変数の実際の型が StringList であることを認識しているためであり、それが関数呼び出しを解決する方法です。いくつかの抽象化レイヤーを実装するための気の利いた小さな事実は、いくつかの .NET コア インターフェイスに蓄積されているようです。

于 2008-09-27T11:59:02.037 に答える
37

C# 経由の CLR から Jeffrey Richter を引用するに (
EIMI、明示的なインターフェイス メソッドI実装を意味します)

EIMI を使用する際に存在する影響を理解することは非常に重要です。このような影響があるため、EIMI をできる限り回避する必要があります。幸いなことに、ジェネリック インターフェイスを使用すると、EIMI をかなり回避できます。ただし、それらを使用する必要がある場合もあります (同じ名前とシグネチャを持つ 2 つのインターフェイス メソッドを実装するなど)。EIMI の大きな問題は次のとおりです。

  • 型が具体的に EIMI メソッドを実装する方法を説明するドキュメントはなく、Microsoft Visual Studio の IntelliSenseサポートもありません。
  • 値型のインスタンスは、インターフェイスにキャストするときにボックス化されます。
  • EIMI は派生型から呼び出すことはできません。

インターフェイス参照を使用する場合、任意の派生クラスで任意の仮想チェーンを明示的に EIMI に置き換えることができ、そのような型のオブジェクトがインターフェイスにキャストされると、仮想チェーンは無視され、明示的な実装が呼び出されます。それは多態性ではありません。

EIMI を使用して、IEnumerable<T> などの基本的なフレームワーク インターフェイスの実装から厳密に型指定されていないインターフェイス メンバーを非表示にすることもできるため、クラスは厳密に型指定されていないメソッドを直接公開しませんが、構文は正しいです。

于 2011-02-09T18:40:36.297 に答える
35

理由その1

「実装へのプログラミング」を思いとどまらせたいときは、明示的なインターフェイス実装を使用する傾向があります(デザインパターンのデザイン原則)。

たとえば、MVPベースの Web アプリケーションでは次のようになります。

public interface INavigator {
    void Redirect(string url);
}

public sealed class StandardNavigator : INavigator {
    void INavigator.Redirect(string url) {
        Response.Redirect(url);
    }
}

別のクラス ( presenterなど) が StandardNavigator 実装に依存する可能性は低くなり、 INavigator インターフェイスに依存する可能性が高くなります ( Redirect メソッドを使用するには、実装をインターフェイスにキャストする必要があるため)。

理由その2

明示的なインターフェイスの実装を使用するもう 1 つの理由は、クラスの「デフォルト」インターフェイスをクリーンに保つことです。たとえば、ASP.NETサーバー コントロールを開発している場合、次の 2 つのインターフェイスが必要になることがあります。

  1. Web ページの開発者が使用する、クラスの主要なインターフェイス。と
  2. コントロールのロジックを処理するために私が開発した、プレゼンターが使用する「隠し」インターフェイス

以下に簡単な例を示します。これは、顧客を一覧表示するコンボ ボックス コントロールです。この例では、Web ページの開発者はリストの作成に関心がありません。代わりに、GUID で顧客を選択したり、選択した顧客の GUID を取得したりしたいだけです。プレゼンターは、最初のページの読み込み時にボックスに入力し、このプレゼンターはコントロールによってカプセル化されます。

public sealed class CustomerComboBox : ComboBox, ICustomerComboBox {
    private readonly CustomerComboBoxPresenter presenter;

    public CustomerComboBox() {
        presenter = new CustomerComboBoxPresenter(this);
    }

    protected override void OnLoad() {
        if (!Page.IsPostBack) presenter.HandleFirstLoad();
    }

    // Primary interface used by web page developers
    public Guid ClientId {
        get { return new Guid(SelectedItem.Value); }
        set { SelectedItem.Value = value.ToString(); }
    }

    // "Hidden" interface used by presenter
    IEnumerable<CustomerDto> ICustomerComboBox.DataSource { set; }
}

プレゼンターはデータ ソースにデータを入力するので、Web ページの開発者はその存在を意識する必要はありません。

しかし、それは銀の砲弾ではありません

明示的なインターフェイスの実装を常に採用することはお勧めしません。これらは、役立つ可能性のある 2 つの例にすぎません。

于 2010-06-14T01:31:06.437 に答える
19

既に述べた他の理由に加えて、これはクラスが同じ名前と署名を持つプロパティ/メソッドを持つ 2 つの異なるインターフェイスを実装している状況です。

/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
    string Title { get; }
    string ISBN { get; }
}

/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
    string Title { get; }
    string Forename { get; }
    string Surname { get; }
}

/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
    /// <summary>
    /// This method is shared by both Book and Person
    /// </summary>
    public string Title
    {
        get
        {
            string personTitle = "Mr";
            string bookTitle = "The Hitchhikers Guide to the Galaxy";

            // What do we do here?
            return null;
        }
    }

    #region IPerson Members

    public string Forename
    {
        get { return "Lee"; }
    }

    public string Surname
    {
        get { return "Oades"; }
    }

    #endregion

    #region IBook Members

    public string ISBN
    {
        get { return "1-904048-46-3"; }
    }

    #endregion
}

このコードは正常にコンパイルおよび実行されますが、Title プロパティは共有されています。

明らかに、返される Title の値は、Class1 を Book として扱うか、Person として扱うかによって異なります。これは、明示的なインターフェイスを使用できる場合です。

string IBook.Title
{
    get
    {
        return "The Hitchhikers Guide to the Galaxy";
    }
}

string IPerson.Title
{
    get
    {
        return "Mr";
    }
}

public string Title
{
    get { return "Still shared"; }
}

明示的なインターフェイス定義は Public であると推論されることに注意してください。したがって、明示的に public (またはそれ以外) であると宣言することはできません。

また、(上記のように) 「共有」バージョンを使用することもできますが、これは可能ですが、そのようなプロパティの存在は疑わしいことに注意してください。おそらく、Title のデフォルトの実装として使用できるので、Class1 を IBook または IPerson にキャストするために既存のコードを変更する必要はありません。

「共有」(暗黙の) タイトルを定義しない場合、Class1 のコンシューマは最初に Class1 のインスタンスを IBook または IPerson に明示的にキャストする必要があります。そうしないと、コードはコンパイルされません。

于 2008-10-01T13:08:01.167 に答える
17

ほとんどの場合、明示的なインターフェイスの実装を使用します。主な理由は次のとおりです。

リファクタリングはより安全です

インターフェイスを変更するときは、コンパイラがそれをチェックできるとよいでしょう。これは、暗黙的な実装ではより困難です。

次の 2 つの一般的なケースが思い浮かびます。

  • このインターフェースを実装する既存のクラスが、たまたま新しいものと同じシグネチャを持つメソッドをすでに持っている場合に、インターフェースに関数を追加する。これにより、予期しない動作が発生する可能性があり、私は何度か苦しめられました。その関数はファイル内の他のインターフェイス メソッドと一緒に配置されていない可能性が高いため、デバッグ時に "確認" するのは困難です (後述の自己文書化の問題)。

  • インターフェイスから関数を削除します。暗黙的に実装されたメソッドは突然デッド コードになりますが、明示的に実装されたメソッドはコンパイル エラーによってキャッチされます。デッドコードは手元に置いておくのが良いとしても、強制的に見直して宣伝したい。

残念なことに、C# には、メソッドを暗黙的な実装としてマークするよう強制するキーワードがないため、コンパイラが追加のチェックを行うことができます。仮想メソッドには、'override' と 'new' を使用する必要があるため、上記の問題のいずれもありません。

注: 固定またはめったに変更されないインターフェイス (通常はベンダー API から) の場合、これは問題ではありません。ただし、私自身のインターフェースについては、いつどのように変更されるかを予測できません。

自己記録です

クラスに 'public bool Execute()' が表示された場合、それがインターフェイスの一部であることを確認するには、余分な作業が必要になります。誰かがおそらくそう言ってコメントするか、他のインターフェース実装のグループに入れなければならないでしょう。すべてリージョンまたはグループ化コメントの下に「ITask の実装」と書かれています。もちろん、これはグループ ヘッダーが画面外にない場合にのみ機能します。

一方、「bool ITask.Execute()」は明確で明確です。

インターフェイス実装の明確な分離

インターフェイスは、具体的な型の表面領域を少しだけ公開するように作成されているため、パブリック メソッドよりも「パブリック」であると考えています。それらは、タイプを機能、振る舞い、特性のセットなどに減らします。実装では、この分離を維持することが役立つと思います。

クラスのコードを調べているときに、明示的なインターフェイスの実装に出くわすと、私の脳は「コード コントラクト」モードに移行します。多くの場合、これらの実装は単純に他のメソッドに転送されますが、追加の状態/パラメーター チェック、内部要件により適合するための受信パラメーターの変換、またはバージョン管理の目的での変換 (つまり、複数世代のインターフェイスがすべて共通の実装に移行する) を行うこともあります。

(パブリックもコード コントラクトであることは認識していますが、特に具体的な型を直接使用することが通常内部専用コードの兆候であるインターフェイス駆動型のコードベースでは、インターフェイスの方がはるかに強力です。)

関連:Jonによる上記の理由2

等々

さらに、ここの他の回答ですでに述べた利点:

問題

楽しいこと、幸せなことばかりではありません。私が暗黙に固執するいくつかのケースがあります:

  • 値の型。これには、ボクシングとより低いパフォーマンスが必要になるためです。これは厳密な規則ではなく、インターフェイスとその使用目的によって異なります。匹敵する?暗黙。フォーマット可能? おそらく明示的です。
  • 頻繁に直接呼び出されるメソッド (IDisposable.Dispose など) を持つ単純なシステム インターフェイス。

また、実際に具象型があり、明示的なインターフェイス メソッドを呼び出したい場合、キャストを行うのは面倒な場合があります。私はこれを次の 2 つの方法のいずれかで処理します。

  1. パブリックを追加し、実装のためにインターフェイス メソッドをそれらに転送します。通常、内部で作業する場合、より単純なインターフェイスで発生します。
  2. (私の好みの方法) a public IMyInterface I { get { return this; } }(インライン化する必要があります) を追加し、 を呼び出しますfoo.I.InterfaceMethod()。この機能を必要とする複数のインターフェイスがある場合は、名前を I 以外に拡張します (私の経験では、この機能が必要になることはめったにありません)。
于 2014-08-15T08:27:10.507 に答える
8

明示的に実装する場合、インターフェイスのタイプの参照を介してのみインターフェイスメンバーを参照できます。実装クラスのタイプである参照は、それらのインターフェースメンバーを公開しません。

クラスの作成に使用されるメソッド(ファクトリまたはIoCコンテナの場合もあります)とインターフェイスメソッド(もちろん)を除いて、実装クラスがパブリックでない場合、明示的に実装することに利点はありません。インターフェイス。

それ以外の場合は、インターフェイスを明示的に実装することで、具体的な実装クラスへの参照が使用されないようにし、後でその実装を変更できるようにします。「確かに」は「利点」だと思います。適切に因数分解された実装は、明示的な実装なしでこれを達成できます。

私の意見では、不利な点は、非公開メンバーにアクセスできる実装コードのインターフェイスとの間で型をキャストしていることに気付くということです。

多くのものと同様に、長所は短所です(逆もまた同様です)。インターフェイスを明示的に実装すると、具象クラスの実装コードが公開されなくなります。

于 2008-09-27T11:12:33.380 に答える
6

暗黙的なインターフェイスの実装は、インターフェイスの同じシグネチャを持つメソッドを持つ場所です。

明示的なインターフェイスの実装では、メソッドが属するインターフェイスを明示的に宣言します。

interface I1
{
    void implicitExample();
}

interface I2
{
    void explicitExample();
}


class C : I1, I2
{
    void implicitExample()
    {
        Console.WriteLine("I1.implicitExample()");
    }


    void I2.explicitExample()
    {
        Console.WriteLine("I2.explicitExample()");
    }
}

MSDN:暗黙的および明示的なインターフェイスの実装

于 2011-05-26T14:12:52.827 に答える
2

明示的なインターフェース実装の重要な用途の 1 つは、可視性が混在するインターフェースを実装する必要がある場合です。

問題と解決策は、記事C# Internal Interfaceで詳しく説明されています。

たとえば、アプリケーション レイヤー間でのオブジェクトの漏えいを防止する場合、この手法を使用すると、漏えいの原因となるメンバーのさまざまな可視性を指定できます。

于 2012-11-03T22:56:38.833 に答える