質問

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

暗黙的に使用するのはいつ、明示的に使用するのですか?

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


Microsoftの公式ガイドライン(初版 Framework Design Guidelines から)< strong>明示的な実装の使用は、コードに予期しない動作を与えるため、推奨されません。

このガイドラインは、物事をインターフェースとして受け渡ししない場合、非常に IoCの前の時間に有効であると思います。

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

役に立ちましたか?

解決

暗黙的は、クラスのメンバーを介してインターフェイスを定義する場合です。 明示的は、インターフェースのクラス内でメソッドを定義する場合です。紛らわしいように聞こえますが、ここに私が意味するものがあります: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つの実装が必要な場合に明示的に使用します。しかし、私はめったに使用しません。

使用する/使用しない理由は他にもあると思われるため、他にも理由があると確信しています。

次の投稿 をご覧くださいそれぞれの背後にある優れた推論のために、このスレッドで。

他のヒント

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

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

  1. インターフェースを直接操作することで、あなたは認めない、 コードを基礎となる実装に結合します。
  2. たとえば、パブリックプロパティNameを既に持っている場合 あなたのコードとあなたも持っているインターフェイスを実装したい Nameプロパティ、明示的に行うと、2つが分離されます。でも 彼らが同じことをしていれば、私はまだ明示的に委任します Nameプロパティを呼び出します。あなたは決して知らない、あなたは変えたいと思うかもしれない Nameが通常のクラスでどのように機能するか、Nameがどのようにインターフェイスするか プロパティは後で機能します。
  3. インターフェイスを暗黙的に実装すると、クラスが公開されます のクライアントにのみ関連する可能性がある新しい動作 インターフェイスとそれはあなたがあなたのクラスを簡潔に保っていないことを意味します 十分です(私の意見)。

すでに提供された優れた回答に加えて、コンパイラが必要なものを把握できるようにするために明示的な実装が必要な場合があります。かなり頻繁に出てくる可能性が高い主な例として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>IEnumerableを実装するため、これも必要です。ただし、汎用バージョンと通常バージョンの両方で、同じメソッドシグネチャを持つ関数が実装されている(C#はこの場合の戻り値の型を無視します)。これは完全に合法で問題ありません。コンパイラは、使用するものをどのように解決しますか?せいぜい暗黙の定義を1つだけ持つように強制すると、必要なものは何でも解決できます。

ie。

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:関数内では、コンパイラーは変数の実際の型がStringListであり、それが関数呼び出しをどのように解決するかをコンパイラーが知っているため、IEnumerableの明示的な定義の間接的な小さな部分が機能します。いくつかの抽象化層を実装するための気の利いた事実は、.NETコアインターフェイスのいくつかが蓄積したようです。

理由#1

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

たとえば、 MVP ベースのWebアプリケーション:

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

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

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

理由#2

明示的なインターフェイスの実装を行うもう1つの理由は、クラスの<!> quot; default <!> quot;を保持することです。インターフェースクリーナー。たとえば、 ASP.NET サーバーコントロールを開発している場合、2つのインターフェイスが必要になる場合があります。 :

  1. Webページ開発者が使用するクラスのプライマリインターフェイス。そして
  2. <!> quot; hidden <!> quot;コントロールのロジックを処理するために開発したプレゼンターが使用するインターフェイス

簡単な例を次に示します。これは、顧客をリストするコンボボックスコントロールです。この例では、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つの例にすぎません。

C#を介してCLRからJeffrey Richterを引用するには
EIMI は、 E 明示的な I インターフェイス M 方法 I 実装を意味します)

  

次のことが非常に重要です   いくつかの影響を理解する   EIMIを使用する場合に存在します。そして   これらの影響、あなたがしようとする必要があります   可能な限りEIMIを避けてください。   幸いなことに、汎用インターフェイスは役立ちます   EIMIをかなり避けます。しかし、そこに   まだ必要なときかもしれません   それらを使用する(2つの実装など)   同じ名前のインターフェースメソッド   および署名)。ここに大きい   EIMIの問題:

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

インターフェイス参照を使用する場合、任意の派生クラスで任意の仮想チェーンを明示的にEIMIに置き換えることができ、そのようなタイプのオブジェクトがインターフェイスにキャストされると、仮想チェーンは無視され、明示的な実装が呼び出されます。それはポリモーフィズム以外の何かです。

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

すでに述べた他の理由に加えて、これはクラスが同じ名前と署名を持つプロパティ/メソッドを持つ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プロパティは共有されます。

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

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

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

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

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

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

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

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

リファクタリングの方が安全です

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

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

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

  • インターフェースから関数を削除する。暗黙的に実装されたメソッドは突然デッドコードになりますが、明示的に実装されたメソッドはコンパイルエラーに巻き込まれます。たとえデッドコードが存在し続けるのが良いとしても、私はそれをレビューし、それを促進することを余儀なくされたい。

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

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

自己文書化

クラスに 'public bool Execute()'が表示される場合、インターフェイスの一部であることを理解するために余分な作業が必要になります。誰かがそれをコメントするか、他のインターフェイス実装のグループに入れて、すべて<!> quot; implementation of ITask <!> quot;というリージョンまたはグループ化コメントの下に置く必要があります。もちろん、グループヘッダーが画面外にない場合にのみ機能します。

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

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

インターフェイスは、パブリックメソッドよりも「パブリック」であると考えています。これらは、具体的な型のほんのわずかな表面積を公開するように作られているからです。それらは型を能力、振る舞い、特性のセットなどに減らします。そして、実装では、この分離を保つことが有用だと思います。

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

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

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

など

さらに、他の回答ですでに述べた利点をここに追加します。

問題

すべてが楽しくて幸せというわけではない。暗黙のうちに固執する場合があります:

  • 値型。ボクシングと低いパフォーマンスが必要になるため。これは厳密なルールではなく、インターフェイスとその意図に依存します中古。 IComparable?暗黙。 IFormattable?おそらく明示的。
  • 頻繁に直接呼び出されるメソッド(IDisposable.Disposeなど)を持つ簡易システムインターフェイス。

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

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

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

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

それ以外の場合、インターフェイスを明示的に実装すると、具体的な実装クラスへの参照が使用されなくなり、後でその実装を変更できるようになります。 <!> quot;確認します<!> quot;は、<!> quot; advantage <!> quot;だと思います。適切にファクタリングされた実装は、明示的な実装なしでこれを達成できます。

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

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

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

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

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: 暗黙的および明示的なインターフェースの実装

インターフェイスを実装するすべてのクラスメンバーは、VB.NETインターフェイス宣言の記述方法と意味的に類似した宣言をエクスポートします。例:

Public Overridable Function Foo() As Integer Implements IFoo.Foo

クラスメンバの名前はインターフェイスメンバの名前と一致することが多く、クラスメンバは多くの場合パブリックになりますが、どちらも必須ではありません。宣言することもできます:

Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo

この場合、クラスとその派生クラスは名前IFoo_Fooを使用してクラスメンバーにアクセスできますが、外部はIFooにキャストすることによってその特定のメンバーにのみアクセスできます。このようなアプローチは、インターフェースメソッドがすべての実装で指定の動作を行うが、一部の実装でのみ有用の動作を行う場合によく適しています。読み取り専用コレクションのIList<T>.Addメソッドに指定された動作は、NotSupportedException]をスローすることです。残念ながら、C#でインターフェイスを実装する唯一の適切な方法は次のとおりです。

int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }

あまり良くない。

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

問題と解決策については、 C#内部インターフェイスの記事で詳しく説明されています。 >

たとえば、アプリケーション層間のオブジェクトの漏洩を保護したい場合、この手法を使用すると、漏洩の原因となる可能性のあるメンバーの異なる可視性を指定できます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top