C#でインターフェース暗黙的にと明示的にを実装する上での違いは何ですか?
暗黙的に使用するのはいつですか、明示的に使用するのはいつですか?
どちらかに賛否両論がありますか。
Microsoftの公式ガイドライン(初版から Framework Design Guidelines )では、コードに予期しない動作が発生するため、明示的な実装の使用は推奨されませんと記述されています。
私はこのガイドラインは非常に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つの実装が必要なときです。それにもかかわらず、私はめったに使用しません。
他の人が投稿するのに使う、使わない理由はもっとあると思います。
それぞれの背後にある優れた推論については、このスレッドの 次の投稿 を参照してください。
暗黙の定義は単にインターフェースによって要求されるメソッド/プロパティなどをパブリックメソッドとしてクラスに直接追加することです。
明示的な定義では、インターフェースを直接操作している場合にのみメンバーが公開され、基礎となる実装は強制されません。ほとんどの場合、これが推奨されます。
すでに提供されている優れた答えに加えて、何が要求されているのかを理解できるようにするためには、明示的な実装が必要な場合があります。 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();
シモンズ:IEnumerableの明示的な定義での小さな間接参照は、関数の内部では変数の実際の型がStringListであることをコンパイラが知っているので、関数呼び出しを解決する方法です。いくつかの抽象化層を実装するための50のちょっとした事実がいくつかの.NETコアインターフェースに蓄積されているようです。
「実装へのプログラミング」をしたくない場合は、明示的なインタフェース実装を使用する傾向があります(デザインパターンからのデザイン原則) 。
たとえば、 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メソッド).
私が明示的なインターフェースの実装に取り掛かる可能性があるもう1つの理由は、クラスの「デフォルト」インターフェースをよりクリーンに保つことです。たとえば、 ASP.NET サーバーコントロールを開発している場合、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つの例です。
C#を介してCLRからJeffrey Richterを引用するには
(EIMIは、Explicitを意味しますInterfaceMethodImplementation)
あなたがEIMIを使用するときに存在するいくつかの影響を理解することはあなたにとって非常に重要です。そしてこれらの影響のために、あなたはできるだけEIMIを避けようとするべきです。幸いなことに、汎用インターフェースはEIMIをかなり回避するのに役立ちます。ただし、それらを使用する必要がある場合もあります(同じ名前と署名を持つ2つのインタフェースメソッドを実装するなど)。これがEIMIに関する大きな問題です。
- 型がEIMIメソッドを具体的にどのように実装するかを説明する文書はなく、Microsoft Visual Studio IntelliSense のサポートもありません。
- 値型インスタンスは、インタフェースにキャストされたときにボックス化されます。
- EIMIは派生型から呼び出すことはできません。
インターフェース参照を使用する場合、任意の派生クラスで任意の仮想チェーンを明示的にEIMIに置き換えることができ、そのようなタイプのオブジェクトがインターフェースにキャストされると、仮想チェーンは無視され、明示的実装が呼び出されます。それは多態性以外の何でもです。
EIMIは、IEnumerable <T>などの基本的なフレームワークインタフェースの実装から、強く型付けされていないインタフェースメンバを非表示にするためにも使用できます。
すでに述べた他の理由に加えて、これはクラスが同じ名前とシグネチャを持つプロパティ/メソッドを持つ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にキャストするために既存のコードを変更する必要はありません。
"shared"(暗黙の)Titleを定義しない場合、Class1の消費者はmust最初にClass1のインスタンスを明示的にIBookまたはIPersonにキャストします。
私はほとんどの場合明示的なインターフェイス実装を使用しています。これが主な理由です。
リファクタリングはより安全です
インタフェースを変更するとき、コンパイラがそれをチェックすることができればより良いです。これは暗黙的な実装では困難です。
2つの一般的なケースが思い浮かびます。
インタフェースへの関数の追加。このインタフェースを実装する既存のクラスは、新しいものと同じシグネチャを持つメソッドをすでに持っています。これは予期しない動作を引き起こす可能性があり、何度か私を苦労させました。その関数はファイル内の他のインタフェースメソッドでは見つからない可能性があるため、デバッグ時に「見る」のは困難です(後述の自己文書化の問題)。
インターフェースから関数を削除する。暗黙的に実装されたメソッドは突然死んだコードになりますが、明示的に実装されたメソッドはコンパイルエラーに巻き込まれます。デッドコードが回避してもよい場合でも、私はそれを見直して宣伝することを強いられます。
C#にメソッドを暗黙の実装としてマークするように強制するキーワードがないのは残念です。そのため、コンパイラは追加のチェックを行うことができます。仮想メソッドは 'override'と 'new'の必須の使用のために上記の問題のどちらも持っていません。
注:固定された、またはめったに変更されないインタフェース(通常はベンダAPIのインタフェース)では、これは問題になりません。しかし、私自身のインターフェースでは、いつ/どのように変わるのか予測できません。
自己文書化されています
クラス内に「public bool Execute()」と表示されている場合、それがインタフェースの一部であることを理解するために余分な作業が必要になります。誰かがそう言うようにそれをコメントする必要があるでしょう、またはそれを他のインターフェース実装のグループに入れる、すべてが「ITaskの実装」と言う領域またはグループ化コメントの下に。もちろん、これはグループヘッダーが画面外にない場合にのみ機能します。
一方、 'bool ITask.Execute()'は明確で明確です。
インターフェース実装の明確な分離
インターフェイスは、パブリックメソッドよりも「パブリック」であると思います。これらは、具体的な型のほんのわずかな表面積を公開するように作られているからです。それらはタイプを能力、行動、特性のセットなどに還元します。そして実装において、私はこの分離を保つことが役に立つと思います。
クラスのコードを見ているうちに、明示的なインターフェイス実装に出くわすと、私の頭脳は「コード契約」モードに移行します。多くの場合、これらの実装は他のメソッドに転送するだけですが、余分な状態/パラメータチェック、内部要件に合わせた受信パラメータの変換、またはバージョン管理の目的での変換さえも行います。
(私は、パブリックもコード契約であることを認識していますが、具体的な型の直接使用が通常内部専用コードの兆候であるインターフェース駆動型コードベースでは、インターフェースははるかに強力です)
関連: Jonによる上記の理由2 。
など
さらにここで他の答えですでに述べた利点:
それはすべての楽しさと幸福ではありません。暗黙のうちに固執する場合がいくつかあります。
また、実際に具象型を持ち、明示的なインターフェースメソッドを呼び出したい場合は、キャストを行うのが面倒になることがあります。これに対処する方法は2つあります。
public IMyInterface I { get { return this; } }
(これはインラインになるはずです)を追加してfoo.I.InterfaceMethod()
を呼び出します。この機能を必要とする複数のインターフェースがある場合は、私を超えて名前を広げてください(私の経験では、これが必要になることは稀です)。明示的に実装した場合、あなたはインターフェースの型である参照を通してのみインターフェースメンバーを参照することができます。実装クラスの型である参照は、それらのインタフェースメンバを公開しません。
実装クラスがパブリックではない場合、クラスの作成に使用されたメソッド(ファクトリまたは IoC container)、およびインターフェイスメソッドを除いて(もちろん)、明示的にインターフェースを実装することの利点を見てください。
それ以外の場合は、明示的にインターフェースを実装することで、具象実装クラスへの参照が使用されないようにして、後でその実装を変更できるようにします。 「確かめる」ことが「利点」だと思います。十分に因数分解された実装は、明示的な実装なしでこれを達成することができます。
私の考えでは、不利な点は、非公開メンバーにアクセスできる実装コードで、インターフェースとの間で型をキャストすることに気付くことです。
多くのことと同様に、利点は欠点です(そしてその逆もあります)。明示的にインターフェースを実装すると、具象クラス実装コードが公開されないようになります。
暗黙のインターフェース実装は、インターフェースの同じシグニチャーを持つメソッドがあるところです。
明示的インターフェイス実装は、メソッドがどのインターフェイスに属するかを明示的に宣言する場所です。
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#内部インタフェースで詳しく説明されています。
たとえば、アプリケーションレイヤ間のオブジェクトの漏洩を保護したい場合は、この方法を使用すると、漏洩の原因となる可能性があるメンバの異なる可視性を指定できます。