C#インターフェイス。暗黙的な実装と明示的な実装


632

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

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

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


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

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

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


C#インターフェイスに関する記事全文を読む:planetofcoders.com/c-interfaces
Gaurav Agrawal

はい、明示的なインターフェイスは避けてください。より専門的なアプローチはISP(インターフェイス分離の原則)を実装するためのものです。同じcodeproject.com/Articles/1000374/…の
Shivprasad Koirala

回答:


492

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

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

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


8
私はこの投稿が古いことを知っていますが、それは非常に便利であることがわかりました-例では暗黙的なものにpublicキーワードがあるというのが私にはわからなかったために明確でない場合に注意すべきことの1つは、エラーが発生することです。
jharr100 2014年

C#4 ed ch 13を介したJeffrey RichterのCLRは、キャストが不要な例を示しています:internal struct SomeValueType:IComparable {private Int32 m_x; public SomeValueType(Int32 x){m_x = x; } public Int32 CompareTo(SomeValueType other){...);} Int32 IComparable.CompareTo(Object other){return CompareTo((SomeValueType)other); }} public static void Main(){SomeValueType v = new SomeValueType(0); オブジェクトo = new Object(); Int32 n = v.CompareTo(v); //ボクシングなしn = v.CompareTo(o); //コンパイル時エラー}
Andy Dent '26

1
今日、明示的なインターフェイスの使用を必要とするまれな状況に遭遇しました:フィールドをプライベートとして作成するインターフェイスビルダーによって生成されたフィールドを持つクラス(XamarinはiOSをターゲットとしており、iOSストーリーボードを使用しています)。そして、そのフィールドを公開することが理にかなっているインターフェイス(パブリック読み取り専用)。インターフェースでゲッターの名前を変更することもできましたが、既存の名前がオブジェクトの最も論理的な名前でした。そのため、代わりにプライベートフィールドを参照する明示的な実装を行いましたUISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }
ToolmakerSteve 2016

1
プログラミングの恐怖。まあ、うまくまとまりました!
リキッドコア

1
@ToolmakerSteve少なくとも1つのインターフェイスメンバーの明示的な実装を必要とする別の状況(より一般的な)は、同じシグネチャを持つが戻り値の型が異なるメンバーを持つ複数のインターフェイスを実装しています。これは、と同じように、ためのインターフェースの継承の発生する可能性がありますIEnumerator<T>.CurrentIEnumerable<T>.GetEnumerator()ISet<T>.Add(T)。これは別の答えで言及されています
phoog

201

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

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

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

2
あなたはここでいくつかの良い点を作ります。特にA.通常はとにかくクラスをインターフェイスとして渡しますが、その観点からそれを実際に考えたことはありません。
mattlant 2008

5
ポイントCに同意するかどうかはわかりません。CatオブジェクトはIEatableを実装している可能性がありますが、Eat()は基本的なものです。IEatableインターフェースではなく「raw」オブジェクトを使用しているときに、CatでEat()を呼び出すだけの場合もありますよね?
LegendLength 2009年

59
Cat確かIEatableに異議のない場所がいくつかあります。
ウンベルト

26
私は上記のすべてに完全に同意しません。明示的なインターフェースを使用することは災害のレシピであり、それらの定義によるOOPまたはOODではない、と言います(ポリモーフィズムに関する私の回答を参照)
Valentin Kuzub


68

すでに提供されている優れた回答に加えて、コンパイラが必要なものを理解できるように明示的な実装が必要となる場合があります。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つの暗黙の定義だけを持たせることを強制し、それからそれはそれが必要とするものは何でも解決することができます。

すなわち。

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コアインターフェイスを蓄積しているようです。


5
@タサダケ:2年後、私が言えることは「良い質問」だけです。たぶん私が取り組んでいたものからそのコードをどこにコピーしたかを除いて、私にはわかりませんabstract
Matthew Scharley、2011年

4
@Tassadaque、あなたは正しいですが、上記のMatthew Scharleyの投稿のポイントは失われていないと思います。
funkymushroom 2016

35

C#のCLR経由してからジェフリー・リヒターを引用する
EIMIが意味E xplicit I nterface Mは ethod Iの mplementation)

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

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

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

EIMIを使用して、IEnumerable <T>などの基本的なフレームワークインターフェイスの実装から強く型付けされていないインターフェイスメンバーを隠すこともできるので、クラスは強く型付けされていないメソッドを直接公開せず、構文的に正しいです。


2
インターフェースの再実装は、合法ではありますが、一般的には疑わしいものです。明示的な実装は一般に、直接、または派生クラスにバインドする必要のあるラッピングロジックを通じて仮想メソッドにチェーンする必要があります。インターフェースを適切なOOP規則に反する方法で使用することは確かに可能ですが、それはそれらがよりよく使用できないことを意味しません。
スーパーキャット2014年

4
@ Valentin EIMIとIEMIは何の略ですか?
ジエニー2014年

明示的なインターフェイスメソッドの実装
scobi 14

5
-1「一般的に、インターフェースはセミ(最高でも)のOOP機能と見なされ、継承を提供しますが、実際のポリモーフィズムを提供しません。」私は強く反対します。それとは正反対に、インターフェースはすべてポリモーフィズムに関するものであり、主に継承に関するものではありません。複数の分類を1つの型に帰します。可能な場合はIEMIを避け、できない場合は@supercatが提案するように委任します。インターフェイスを避けないでください。
Aluan Haddad 2016

1
「EIMIを派生型から呼び出すことはできません。」<<何?それは真実ではない。型にインターフェイスを明示的に実装した場合、その型から派生した場合でも、それをインターフェイスにキャストしてメソッドを呼び出すことができます。実装されている型の場合とまったく同じです。だからあなたが何を話しているのかわからない。派生型内でも、問題のインターフェイスに「this」をキャストするだけで、明示的に実装されたメソッドに到達できます。
Triynko 2018年

34

理由#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つの例にすぎません。


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"; }
}

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

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

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


16

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

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

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

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

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

  • インターフェイスから関数を削除します。暗黙的に実装されたメソッドは突然デッドコードになりますが、明示的に実装されたメソッドはコンパイルエラーによって捕捉されます。死んだコードを残しておくのは良いことだとしても、私はそれをレビューして宣伝することを強いられたいです。

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

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

それは自己文書化です

クラスに「public bool Execute()」が表示される場合、それがインターフェイスの一部であることを理解するために追加の作業が必要になります。おそらく誰かがそのようにコメントするか、他のインターフェース実装のグループに入れて、すべて「ITaskの実装」と言う領域またはグループ化コメントの下に置く必要があります。もちろん、これはグループヘッダーがオフスクリーンでない場合にのみ機能します。

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

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

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

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

(パブリックもコードコントラクトであることに気付きましたが、特に、具象型を直接使用することが内部のみのコードのサインであるインターフェース駆動型のコードベースでは、インターフェースがはるかに強力です。)

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

等々

さらに、ここで他の回答ですでに言及されている利点:

問題

すべてが楽しくて幸せなわけではありません。私は暗黙のうちにこだわるいくつかのケースがあります:

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

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

  1. パブリックを追加し、実装のためにインターフェースメソッドを転送します。通常、内部で作業するときに、より単純なインターフェースで発生します。
  2. (私の推奨メソッド)public IMyInterface I { get { return this; } }(インライン化する必要があります)を追加してを呼び出しますfoo.I.InterfaceMethod()。この機能を必要とする複数のインターフェースがある場合は、名前を私を超えて拡張します(私の経験では、この必要があることはまれです)。

8

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

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

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

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

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


1
いい答えだ、ビル。他の答えは素晴らしかったですが、あなたが私が理解しやすいように(あなたの意見に加えて)いくつかの追加の客観的な視点を提供しました。ほとんどの場合と同様に、暗黙的または明示的な実装には長所と短所があるため、特定のシナリオまたはユースケースに最適なものを使用する必要があります。私はあなたの答えを読むことで利益が得られることをよりよく理解しようとする人たちだと思います。
ダニエルイーグル14

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


6

インターフェイスを実装するすべてのクラスメンバーは、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メソッドで指定された動作はthrowですNotSupportedException)。残念ながら、C#でインターフェイスを実装する唯一の適切な方法は次のとおりです。

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

それほど良くない。


2

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

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

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


2

かもしれC#で明示的インターフェイスを実装する理由は、以前の回答を説明preferrable(主に正式な理由のために)。ただし、明示的な実装が必須である状況が1つあります。インターフェイスがnon- publicであるが、実装するクラスがである場合にカプセル化のリークを回避するためですpublic

// Given:
internal interface I { void M(); }

// Then explicit implementation correctly observes encapsulation of I:
// Both ((I)CExplicit).M and CExplicit.M are accessible only internally.
public class CExplicit: I { void I.M() { } }

// However, implicit implementation breaks encapsulation of I, because
// ((I)CImplicit).M is only accessible internally, while CImplicit.M is accessible publicly.
public class CImplicit: I { public void M() { } }

C#仕様によると、「すべてのインターフェイスメンバーが暗黙的にパブリックアクセスを持っている」ため、上記のリークは避けられません。結果としてpublic、たとえインターフェース自体がであっても、暗黙の実装もアクセスを与える必要がありますinternal

C#での暗黙的なインターフェイス実装は非常に便利です。実際には、多くのプログラマーは、これ以上何も考慮せずにいつでもどこでもそれを使用します。これにより、せいぜい乱雑なタイプの表面になり、最悪の場合、カプセル化が漏れます。F#などの他の言語では許可されていません

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.