次の方法で共有


18 インターフェイス

18.1 全般

インターフェイスによりコントラクトが定義されます。 インターフェイスを実装するクラスまたは構造体は、その契約に従う必要があります。 インターフェイスは複数の基本インターフェイスから継承することができ、クラスまたは構造体は複数のインターフェイスを実装することができます。

インターフェイスには、メソッド、プロパティ、イベント、およびインデクサーを含めることができます。 インターフェイス自体は、宣言するメンバーの実装を提供しません。 インターフェイスは、インターフェイスを実装するクラスまたは構造体によって提供されるメンバーを指定するだけです。

18.2 インターフェイス宣言

18.2.1 全般

interface_declaration は、新しいインターフェイス型を宣言する type_declaration (§14.7) です。

interface_declaration
    : attributes? interface_modifier* 'partial'? 'interface'
      identifier variant_type_parameter_list? interface_base?
      type_parameter_constraints_clause* interface_body ';'?
    ;

interface_declaration は、オプションの属性のセット (§22) の後に、オプションの interface_modifier のセット (§18.2. 2)、オプションの partial 修飾子 (§15.2.7)、キーワード interface とインターフェイスの名前を指定する識別子、オプションの variant_type_parameter_list 仕様 (§18.2.3)、オプションの interface_base 仕様 (§18.2.4)、オプションの type_parameter_constraints_clause 仕様 (§15.2.5)、interface_body (§18.3)、必要に応じてセミコロンが続く形で構成されます。

インターフェイス宣言は、 variant_type_parameter_listも提供していない限り、type_parameter_constraints_clauseを提供してはなりません。

variant_type_parameter_list を提供するインターフェイス宣言は、ジェネリック インターフェイス宣言です。 さらに、ジェネリック インターフェイス宣言またはジェネリック構造体宣言内に入れ子になったクラスは、それ自体がジェネリック インターフェイス宣言です。これは、包含型の型引数を指定して構築型 (§8.4) を作成する必要があるためです。

18.2.2 インターフェイス修飾子

interface_declaration には、必要に応じてインターフェイス修飾子のシーケンスを含めることができます。

interface_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§23.2) は、安全でないコード (§23) でのみ使用できます。

インターフェイス宣言で同じ修飾子が複数回出現するのはコンパイル時のエラーです。

new 修飾子は、クラス内で定義されているインターフェイスでのみ許可されます。 §15.3.5 で説明されているように、インターフェイスが同じ名前で継承されたメンバーを非表示にすることを指定します。

publicprotectedinternal、および private 修飾子は、インターフェイスのアクセシビリティを制御します。 インターフェイス宣言が発生するコンテキストによっては、これらの修飾子の一部のみが許可される場合があります (§7.5.2)。 partial 型宣言 (§15.2.7) にアクセシビリティ仕様 (publicprotectedinternal、および private 修飾子を使用) が含まれている場合、§15.2.2 の規則が適用されます。

18.2.3 バリアント型パラメーター リスト

18.2.3.1 全般

バリアント型パラメーター リストは、インターフェイス型とデリゲート型でのみ使用できます。 通常の type_parameter_list との違いは、各型パラメーターの省略可能な variance_annotation です。

variant_type_parameter_list
    : '<' variant_type_parameter (',' variant_type_parameter)* '>'
    ;

variant_type_parameter
    : attributes? variance_annotation? type_parameter
    ;

variance_annotation
    : 'in'
    | 'out'
    ;

分散注釈が out の場合、型パラメーターは共変と見なされます。 分散注釈が in の場合、型パラメーターは反変と見なされます。 分散注釈がない場合、型パラメーターは不変と見なされます。

: 次の場合、

interface C<out X, in Y, Z>
{
    X M(Y y);
    Z P { get; set; }
}

X は共変であり、Y は反変であり、Z は不変です。

終了サンプル

ジェネリック インターフェイスが複数の部分で宣言されている場合 (§15.2.3)、各部分宣言では、各型パラメーターに同じ分散を指定する必要があります。

18.2.3.2 変動安全性

型の型パラメーター リストに分散注釈が出現すると、型宣言内で型が発生する可能性がある場所が制限されます。

次のいずれかである場合、型 T は output-unsafe です。

  • T は、反変の型パラメーターである。
  • T は、output-unsafe 要素型を持つ配列型である。
  • T は、ジェネリック型 Sᵢ,... Aₑ から構築されるインターフェイス型またはデリゲート型 S<Xᵢ, ... Xₑ> で、少なくとも 1 つの Aᵢ が次のいずれかを保持します。
    • Xᵢ は共変または不変であり、Aᵢ は output-unsafe である。
    • Xᵢ は反変または不変であり、Aᵢ は input-unsafe である。

次のいずれかである場合、型 T は input-unsafe です。

  • T は共変の型パラメーターである。
  • T は、input-unsafe 要素型を持つ配列型である。
  • T は、ジェネリック型 S<Aᵢ,... Aₑ> から構築されるインターフェイス型またはデリゲート型 S<Xᵢ, ... Xₑ> で、少なくとも 1 つの Aᵢ が次のいずれかを保持します。
    • Xᵢ は共変または不変であり、Aᵢ は input-unsafe である。
    • Xᵢ は反変または不変であり、Aᵢ は output-unsafe である。

直感的に、output-unsafe 型は出力位置では禁止され、input-unsafe 型は入力位置で禁止されます。

型は、output-unsafe でない場合は output-safe、input-unsafe でない場合は input-safe です。

18.2.3.3 分散変換

分散注釈の目的は、インターフェイス型とデリゲート型に対してより寛大な (ただし、安全な型) 変換を提供することです。 このために、暗黙的な (§10.2) と明示的な変換 (§10.3) の定義では、分散変換の概念が使用されます。これは次のように定義されています。

T<Aᵢ, ..., Aᵥ> がバリアント型パラメーター T<Bᵢ, ..., Bᵥ> で宣言されたインターフェイスまたはデリゲート型のいずれかであり、各バリアント型パラメーター T について次のいずれかである場合、型 T<Xᵢ, ..., Xᵥ> は、型 Xᵢ に対して分散変換可能です。

  • Xᵢ は共変であり、Aᵢ から Bᵢ への暗黙的な参照または同一性変換が存在する。
  • Xᵢ は反変であり、Bᵢ から Aᵢ への暗黙的な参照または同一性変換が存在する。
  • Xᵢ は不変であり、Aᵢ から Bᵢ への同一性変換が存在する。

18.2.4 base_interface

インターフェイスは、インターフェイスの明示的な基底インターフェイスと呼ばれる、0 個以上のインターフェイス型から継承できます。 インターフェイスに 1 つ以上の明示的な基本インターフェイスがある場合、そのインターフェイスの宣言では、インターフェイス識別子の後に、基本インターフェイス型のコロンとコンマ区切りのリストが続きます。

interface_base
    : ':' interface_type_list
    ;

明示的な基本インターフェイスは、インターフェイス型 (§8.4§18.2) で構築できます。 基底インターフェイスは独自の型パラメーター型にすることはできませんが、スコープ内の型パラメーターを含めることができます。

構築されたインターフェイス型の場合、明示的な基本インターフェイスは、ジェネリック型宣言で明示的な基本インターフェイス宣言を受け取り、基本インターフェイス宣言の各 type_parameter に対して、構築された型の対応する type_argument を置き換えることによって形成されます。

インターフェイスの明示的な基本インターフェイスは、少なくともインターフェイス自体と同じかそれ以上のアクセスが可能である必要があります (§7.5.5)。

: たとえば、private インターフェイスの internal または public インターフェイスを指定するのはコンパイル時エラーです。 注釈

インターフェイスが直接的または間接的に自身を継承すると、コンパイル時にエラーが発生します。

インターフェイスの基本インターフェイスは、明示的な基本インターフェイスとその基本インターフェイスです。 つまり、基本インターフェイスのセットは、明示的な基本インターフェイス、その明示的な基本インターフェイスのように続く完全な推移的閉包です。 インターフェイスは、その基本インターフェイスのすべてのメンバーを継承します。

: 次のコード例の内容:

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

IComboBox の基本インターフェイスは、IControlITextBox、および IListBox です。 言い換えると、上記の IComboBox インターフェイスは、メンバー SetTextSetItemsPaint を継承します。

終了サンプル

構築されたジェネリック型から継承されたメンバーは、型の置換後に継承されます。 つまり、メンバー内のすべての構成要素型には、class_base 仕様で使用される対応する型引数に置き換えらる、基底クラス宣言の型パラメーターがあります。

: 次のコード例の内容:

interface IBase<T>
{
    T[] Combine(T a, T b);
}

interface IDerived : IBase<string[,]>
{
    // Inherited: string[][,] Combine(string[,] a, string[,] b);
}

インターフェイス IDerived は、型パラメーター CombineT に置き換えられた後、string[,] メソッドを継承します。

終了サンプル

インターフェイスを実装するクラスまたは構造体は、インターフェイスのすべての基本インターフェイスも暗黙的に実装します。

部分インターフェイス宣言 (§15.2.7) の複数の部分でのインターフェイスの処理については、 §15.2.4.3 で詳しく説明しています。

インターフェイスのすべての基本インターフェイスは、output-safe である必要があります (§18.2.3.2)。

18.3 インターフェイス本体

インターフェイスの interface_body は、インターフェイスのメンバーを定義します。

interface_body
    : '{' interface_member_declaration* '}'
    ;

18.4 インターフェイス メンバー

18.4.1 全般

インターフェイスのメンバーは、基本インターフェイスから継承されたメンバーと、インターフェイス自体によって宣言されたメンバーです。

interface_member_declaration
    : interface_method_declaration
    | interface_property_declaration
    | interface_event_declaration
    | interface_indexer_declaration
    ;

インターフェイス宣言は、0 個以上のメンバーを宣言します。 インターフェイスのメンバーは、メソッド、プロパティ、イベント、またはインデクサーである必要があります。 インターフェイスに定数、フィールド、演算子、インスタンス コンストラクター、ファイナライザー、または型を含めることはできません。また、インターフェイスに任意の型の静的メンバーを含めることはできません。

すべてのインターフェイス メンバーには、暗黙的にパブリック アクセス権が与えられます。 インターフェイス メンバー宣言が任意の修飾子を含めるのは、コンパイル時エラーです。

interface_declaration によって新しい宣言空間 (§7.3) が作成され、interface_declaration に含まれる型パラメーターと interface_member_declaration によって、この宣言空間に新しいメンバーが導入されます。 interface_member_declaration には、次の規則が適用されます。

  • インターフェイス宣言の variant_type_parameter_list の型パラメーターの名前は、同じ variant_type_parameter_list 内の他のすべての型パラメーターの名前とは異なり、インターフェイスの名前とクラスのすべてのメンバーの名前とも異なっている必要があります。
  • メソッドの名前は、同じインターフェイスで宣言されているすべてのプロパティとイベントの名前とは異なります。 さらに、メソッドのシグネチャ (§7.6) は、同じインターフェイスで宣言されている他のすべてのメソッドのシグネチャと異なる必要があり、同じインターフェイスで宣言されている 2 つのメソッドには、inout、および ref によってのみ異なるシグネチャは含まれていないものとします。
  • プロパティまたはイベントの名前は、同じインターフェイスで宣言されている他のすべてのメンバーの名前とは異なる必要があります。
  • インデクサーのシグネチャは、同じインターフェイスで宣言されている他のすべてのインデクサーのシグネチャとは異なる必要があります。

インターフェイスの継承されたメンバーは、特にインターフェイスの宣言空間の一部ではありません。 したがって、インターフェイスは、継承されたメンバーと同じ名前またはシグネチャを持つメンバーを宣言できます。 この場合、派生インターフェイス メンバーは、基本インターフェイス メンバーを非表示するものとされています。 継承されたメンバーを非表示にしてもエラーとは見なされませんが、コンパイラによって警告が発行されます。 警告を抑制するために、派生インターフェイス メンバーの宣言には、派生メンバーが基本メンバーを非表示にすることを意図していることを示す new 修飾子を含める必要があります。 このトピックについては、§7.7.2.3 で詳しく説明しています。

継承されたメンバーを非表示にしない宣言に new 修飾子が含まれている場合は、その旨の警告が発行されます。 この警告は、new 修飾子を削除することで抑制されます。

: クラス object のメンバーは、厳密には、どのインターフェイス (§18.4) のメンバーでもありません。 ただし、クラス object のメンバーは、任意のインターフェイス型 (§12.5) のメンバー検索を介して使用できます。 注釈

複数の部分で宣言されたインターフェイスのメンバーのセット (§15.2.7) は、各部分で宣言されたメンバーの和集合です。 インターフェイス宣言のすべての部分の本体は同じ宣言空間 (§7.3) を共有し、各メンバーのスコープ (§7.7) はすべての部分の本体まで拡張します。

18.4.2 インターフェイス メソッド

インターフェイス メソッドは、interface_method_declaration を使用して宣言されます。

interface_method_declaration
    : attributes? 'new'? return_type interface_method_header
    | attributes? 'new'? ref_kind ref_return_type interface_method_header
    ;

interface_method_header
    : identifier '(' parameter_list? ')' ';'
    | identifier type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause* ';'
    ;

インターフェイス メソッド宣言の属性return_typeref_return_type識別子、および parameter_list は、クラス内のメソッド宣言におけるそれらの意味と同じです (§15.6)。 インターフェイス メソッド宣言はメソッド本体を指定することは許可されていないため、宣言は常にセミコロンで終わります。

インターフェイス メソッドのすべてのパラメーター型は、input-safe (§18.2.3.2) で、戻り値の型は void または output-safe のいずれかである必要があります。 さらに、出力または参照パラメーターの型も output-safe である必要があります。

: 出力パラメーターは、一般的な実装制限により、input-safe である必要があります。 注釈

さらに、メソッドの任意の型パラメーターに対する各クラス型制約、インターフェイス型制約、および型パラメーター制約は、input-safe である必要があります。

さらに、メソッドの任意の型パラメーターに対する各クラス型制約、インターフェイス型制約、および型パラメーター制約は、input-safe である必要があります。

これらの規則により、インターフェイスの共変または反変の使用が確実にタイプセーフで維持されます。

:

interface I<out T>
{
    void M<U>() where U : T;     // Error
}

これは、T の型パラメーター制約として U を使用することは input-safe.ではないため、不正な形式です。

この制限が適用されていない場合、次ように型の安全性が損なわれる可能性があります。

class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<U>() {...} 
}

...

I<B> b = new C();
b.M<E>();

これは実際には、C.M<E> への呼び出しです。 しかし、この呼び出しでは ED から派生している必要があるため、ここで型の安全性に違反します。

終了サンプル

18.4.3 インターフェイスのプロパティ

インターフェイスのプロパティは、interface_property_declaration を使用して宣言されます。

interface_property_declaration
    : attributes? 'new'? type identifier '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type identifier '{' ref_interface_accessor '}'
    ;

interface_accessors
    : attributes? 'get' ';'
    | attributes? 'set' ';'
    | attributes? 'get' ';' attributes? 'set' ';'
    | attributes? 'set' ';' attributes? 'get' ';'
    ;

ref_interface_accessor
    : attributes? 'get' ';'
    ;

インターフェイス プロパティ宣言の属性、および識別子は、クラス (§15.7) 内のプロパティ宣言におけるそれらの意味と同じです。

インターフェイス プロパティ宣言のアクセサーは、クラス プロパティ宣言 (§15.7.3) のアクセサーに対応します。ただし、accessor_body は常にセミコロンである必要があります。 したがって、アクセサーは、プロパティが読み取り/書き込み、読み取り専用、書き込み専用のうちのどれであるかを示すだけです。

インターフェイス プロパティの型は、get アクセサーがある場合は output-safe であり、set アクセサーがある場合は input-safe である必要があります。

18.4.4 インターフェイス イベント

インターフェイス イベントは、interface_event_declaration を使用して宣言されます。

interface_event_declaration
    : attributes? 'new'? 'event' type identifier ';'
    ;

インターフェイス イベント宣言の属性、および識別子は、クラス (§15.8) 内のイベント宣言におけるそれらの意味と同じです。

インターフェイス イベントの型は、input-safe である必要があります。

18.4.5 インターフェイス インデクサー

インターフェイス インデクサーは、interface_indexer_declaration を使用して宣言されます。

interface_indexer_declaration
    : attributes? 'new'? type 'this' '[' parameter_list ']'
      '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
      '{' ref_interface_accessor '}'
    ;

インターフェイス インデクサー宣言の属性、および parameter_list は、クラス (§15.9) 内のインデクサー宣言におけるそれらの意味と同じです。

インターフェイス インデクサー宣言のアクセサーは、クラス インデクサー宣言 (§15.7.3) のアクセサーに対応します。ただし、accessor_body は常にセミコロンである必要があります。 したがって、アクセサーは、インデクサーが読み取り/書き込み、読み取り専用、書き込み専用のうちのどれであるかを示すだけです。

インターフェイス インデクサーのすべてのパラメーター型は、input-safe である必要があります (§18.2.3.2)。 さらに、出力または参照パラメーターの型も output-safe である必要があります。

: 出力パラメーターは、一般的な実装制限により、input-safe である必要があります。 注釈

インターフェイス インデクサーの型は、get アクセサーがある場合は output-safe であり、set アクセサーがある場合は input-safe である必要があります。

18.4.6 インターフェイス メンバーへのアクセス

インターフェイス メンバーは、形式 および のメンバー アクセス (I.M) およびインデクサー アクセス (I[A]) 式を通じてアクセスされます。I はインターフェイスの種類、M はそのインターフェイスの種類のメソッド、プロパティ、またはイベント、A はインデクサー引数リストです。

厳密に単一継承のインターフェイス (継承チェーン内の各インターフェイスには、正確に 0 または 1 つの直接ベース インターフェイスがあります) の場合、メンバー検索の効果 (§12.5)、メソッド呼び出し (§12.8.10.2、およびインデクサー アクセス (§12.8.12.3) のルールは、クラスおよび構造体の場合とまったく同じです。 派生メンバーが増えると、同じ名前または署名を持つ派生メンバーで非表示にされるものが少なくなります。 ただし、複数継承インターフェイスの場合、関連のない 2 つ以上の基本インターフェイスが同じ名前またはシグネチャを持つメンバーを宣言すると、あいまいさが発生する可能性があります。 この小項では、いくつかの例を示しています。そのうちの一部は曖昧さを引き起こすもので、他の例は引き起こさないものです。 いずれの場合も、明示的なキャストを使用してあいまいさを解決できます。

: 次のコード例の内容:

interface IList
{
    int Count { get; set; }
}

interface ICounter
{
    int Count { get; set; }
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
        x.Count = 1;             // Error
        ((IList)x).Count = 1;    // Ok, invokes IList.Count.set
        ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count
    }
}

最初のステートメントでは、Countのメンバー参照 (IListCounter) があいまいであるため、コンパイル時エラーが発生します。 例で示すように、あいまいさは、適切な基本インターフェイス型に x をキャストすることによって解決されます。 このようなキャストには実行時コストはかかりません。コンパイル時にインスタンスを派生の少ない型として表示するだけです。

終了サンプル

: 次のコード例の内容:

interface IInteger
{
    void Add(int i);
}

interface IDouble
{
    void Add(double d);
}

interface INumber : IInteger, IDouble {}

class C
{
    void Test(INumber n)
    {
        n.Add(1);             // Invokes IInteger.Add
        n.Add(1.0);           // Only IDouble.Add is applicable
        ((IInteger)n).Add(1); // Only IInteger.Add is a candidate
        ((IDouble)n).Add(1);  // Only IDouble.Add is a candidate
    }
}

呼び出し n.Add(1) は、IInteger.Add のオーバーロード解決規則を適用して を選択します。 同様に、呼び出し n.Add(1.0)IDouble.Add を選択します。 明示的なキャストが挿入されると、候補メソッドは 1 つしか存在しないため、あいまいさは生じません。

終了サンプル

: 次のコード例の内容:

interface IBase
{
    void F(int i);
}

interface ILeft : IBase
{
    new void F(int i);
}

interface IRight : IBase
{
    void G();
}

interface IDerived : ILeft, IRight {}

class A
{
    void Test(IDerived d)
    {
        d.F(1);           // Invokes ILeft.F
        ((IBase)d).F(1);  // Invokes IBase.F
        ((ILeft)d).F(1);  // Invokes ILeft.F
        ((IRight)d).F(1); // Invokes IBase.F
    }
}

IBase.F メンバーは、ILeft.F メンバーによって非表示にされます。 したがって、d.F(1) は、ILeft.F を経由するアクセス パスで非表示にされないように見えますが、呼び出し IBase.FIRight を選択します。

複数継承インターフェイスで非表示にするための直感的なルールは、単純に次のとおりです。メンバーが任意のアクセス パスで非表示の場合、すべてのアクセス パスで非表示になります。 IDerived から ILeft 経由で IBase へのアクセス パスは、IBase.Fを非表示にするため、IDerived から IRight 経由での IBase へのアクセス パスでもそのメンバーは非表示になります。

終了サンプル

18.5 修飾インターフェイス メンバーの名前

インターフェイス メンバーは、修飾インターフェイス メンバー名によって参照されることがあります。 インターフェイス メンバーの修飾名は、メンバーが宣言されているインターフェイスの名前の後にドット、その後にメンバーの名前が続く形で構成されます。 メンバーの修飾名は、メンバーが宣言されているインターフェイスを参照します。

: 次の宣言を指定した場合

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

Paint の修飾名は IControl.Paint であり、SetText の修飾名は ITextBox.SetText です。 上記の例では、Paint としてを ITextBox.Paint 参照することはできません。

終了サンプル

インターフェイスが名前空間の一部である場合、修飾インターフェイス メンバー名に名前空間名を含めることができます。

:

namespace System
{
    public interface ICloneable
    {
        object Clone();
    }
}

System名前空間内では、ICloneable.CloneSystem.ICloneable.Clone の両方が、Clone メソッドの修飾インターフェイス メンバー名です。

終了サンプル

18.6 インターフェイスの実装

18.6.1 全般

インターフェイスは、クラスと構造体によって実装できます。 クラスまたは構造体がインターフェイスを直接実装することを示すには、そのインターフェースをクラスまたは構造体の基底クラス リストに含めます。

:

interface ICloneable
{
    object Clone();
}

interface IComparable
{
    int CompareTo(object other);
}

class ListEntry : ICloneable, IComparable
{
    public object Clone() {...}    
    public int CompareTo(object other) {...}
}

終了サンプル

インターフェイスを直接実装するクラスまたは構造体は、インターフェイスのすべての基本インターフェイスも暗黙的に実装します。 これは、クラスまたは構造体が基底クラス リスト内のすべての基本インターフェイスを明示的に一覧表示しない場合でも同様です。

:

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    public void Paint() {...}
    public void SetText(string text) {...}
}

ここでは、クラス TextBoxIControlITextBox の両方を実装します。

終了サンプル

クラス C がインターフェイスを直接実装すると、C から派生したすべてのクラスも暗黙的に実装されます。

クラス宣言で指定された基本インターフェイスは、構築されたインターフェイス型 (§8.4§18.2) にできます。

: 次のコードは、クラスが構築されたインターフェイス型を実装する方法を示しています。

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

終了サンプル

ジェネリック クラス宣言の基本インターフェイスは、§18.6.3 で説明されている一意性規則を満たす必要があります。

18.6.2 明示的なインターフェイス メンバーの実装

インターフェイスを実装する目的で、クラスまたは構造体は明示的なインターフェイス メンバーの実装を宣言できます。 明示的なインターフェイス メンバーの実装は、修飾インターフェイス メンバー名を参照するメソッド、プロパティ、イベント、またはインデクサーの宣言です。

:

interface IList<T>
{
    T[] GetElements();
}

interface IDictionary<K, V>
{
    V this[K key] { get; }
    void Add(K key, V value);
}

class List<T> : IList<T>, IDictionary<int, T>
{
    public T[] GetElements() {...}
    T IDictionary<int, T>.this[int index] {...}
    void IDictionary<int, T>.Add(int index, T value) {...}
}

ここでは、IDictionary<int,T>.thisIDictionary<int,T>.Add が明示的なインターフェイス メンバーの実装です。

終了サンプル

: 場合によっては、インターフェイス メンバーの名前が実装クラスに適していない場合があります。その場合、インターフェイス メンバーは明示的なインターフェイス メンバーの実装を使用して実装できます。 たとえば、ファイル抽象化を実装するクラスは、ファイル リソースを解放する効果がある Close メンバー関数を実装し、明示的なインターフェイス メンバー実装を使用して Dispose インターフェイスの IDisposable メソッドを実装する可能性があります。

interface IDisposable
{
    void Dispose();
}

class MyFile : IDisposable
{
    void IDisposable.Dispose() => Close();

    public void Close()
    {
        // Do what's necessary to close the file
        System.GC.SuppressFinalize(this);
    }
}

終了サンプル

メソッド呼び出し、プロパティ アクセス、イベント アクセス、またはインデクサー アクセスでは、修飾インターフェイス メンバー名を使用して明示的なインターフェイス メンバーの実装にアクセスすることはできません。 明示的なインターフェイス メンバーの実装は、インターフェイス インスタンスを介してのみアクセスでき、その場合はメンバー名だけで参照されます。

明示的なインターフェイス メンバーの実装で、 または extern 以外の修飾子 (async) を含めるのは、コンパイル時エラーです。

明示的なインターフェイス メソッドの実装は、インターフェイスから任意の型パラメーター制約を継承します。

明示的なインターフェイス メソッド実装におけるtype_parameter_constraints_clauseは、classまたはstructを含み、それが継承された制約によって参照型または値型のいずれかであることが判明しているtype_parameterprimary_constraintを適用する場合に限られます。 明示的なインターフェイス メソッド実装のシグネチャに T? 形式の型 ( T は型パラメーター) は、次のように解釈されます。

  • 型パラメーターclassT制約が追加された場合、T?は null 許容参照型になります。それ以外の場合
  • 制約が追加されていない場合、またはstruct制約が追加された場合、型パラメーターTT?は null 許容値型になります。

: 型パラメーターが関係する場合のルールのしくみを次に示します。

#nullable enable
interface I
{
    void Foo<T>(T? value) where T : class;
    void Foo<T>(T? value) where T : struct;
}

class C : I
{
    void I.Foo<T>(T? value) where T : class { }
    void I.Foo<T>(T? value) where T : struct { }
}

型パラメーター制約 where T : classしないと、参照型型パラメーターを持つ基本メソッドをオーバーライドできません。 終了サンプル

: 明示的なインターフェイス メンバーの実装には、他のメンバーとは異なるアクセシビリティ特性があります。 明示的なインターフェイス メンバーの実装は、メソッド呼び出しまたはプロパティ アクセスで修飾インターフェイス メンバー名を介してアクセスすることはないため、ある意味プライベートです。 ただし、インターフェイスを介してアクセスできるため、宣言されているインターフェイスと同様にパブリックでもあります。 明示的なインターフェイス メンバーの実装は、次の 2 つの主な目的を果たします。

  • 明示的なインターフェイス メンバーの実装はクラスまたは構造体インスタンスを介してアクセスできないため、インターフェイス実装をクラスまたは構造体のパブリック インターフェイスから除外できます。 これは、クラスまたは構造体が、そのクラスまたは構造体のコンシューマーにとって関心のない内部インターフェイスを実装する場合に特に便利です。
  • 明示的なインターフェイス メンバー実装では、同じシグネチャを持つインターフェイス メンバーのあいまいさを解消できます。 明示的なインターフェイス メンバーの実装がない場合、クラスまたは構造体は、同じシグネチャと戻り値型を持つインターフェイス メンバーの異なる実装を持つことはできません。また、クラスまたは構造体は、同じシグネチャを持ち、異なる戻り値型を持つすべてのインターフェイス メンバーで実装を持つこともできません。

注釈

明示的なインターフェイス メンバーの実装を有効にするには、クラスまたは構造体は、修飾インターフェイス メンバー名、型、型パラメーターの数、およびパラメーター型が明示的インターフェイス メンバー実装のメンバーと完全に一致するメンバーを含む、基底クラス リスト内のインターフェイスの名前を指定する必要があります。 インターフェイス関数メンバーにパラメーター配列がある場合、関連付けられている明示的インターフェイス メンバー実装の対応するパラメーターは、params 修飾子を持つことが許可されますが、必須ではありません。 インターフェイス関数メンバーにパラメーター配列がない場合、関連付けられている明示的インターフェイス メンバーの実装には、パラメーター配列は含まれません。

: したがって、次のクラスでは

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
    int IComparable.CompareTo(object other) {...} // invalid
}

IComparable.CompareTo の宣言は、IComparableShape の基底クラスの一覧にリストされておらず、ICloneable の基本インターフェイスではないため、コンパイル時エラーが発生します。 同様に、次の宣言では

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
}

class Ellipse : Shape
{
    object ICloneable.Clone() {...} // invalid
}

ICloneable.Clone の基底クラス リストに Ellipse が明示的にリストされていないため、ICloneableEllipse の宣言によってコンパイル時エラーが発生します。

終了サンプル

明示的インターフェイス メンバー実装の修飾インターフェイス メンバー名は、メンバーが宣言されたインターフェイスを参照する必要があります。

: したがって、次の宣言では

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
}

Paint の明示的インターフェイス メンバーの実装は、IControl.Paint ではなく、ITextBox.Paint と して記述する必要があります。

終了サンプル

18.6.3 実装インターフェイスの一意性

ジェネリック型宣言によって実装されるインターフェイスは、構築可能なすべての型に対して一意である必要があります。 この規則がないと、構築された特定の型を呼び出す正しいメソッドを特定することができません。

: ジェネリック クラス宣言を次のように記述することが許可されているとします。

interface I<T>
{
    void F();
}

class X<U ,V> : I<U>, I<V> // Error: I<U> and I<V> conflict
{
    void I<U>.F() {...}
    void I<V>.F() {...}
}

これが許可されている場合、次の場合に実行すべきコードを特定することができません。

I<int> x = new X<int, int>();
x.F();

終了サンプル

ジェネリック型宣言のインターフェイス リストが有効かどうかを判断するには、次の手順を実行します。

  • ジェネリック クラス、構造体、またはインターフェイス宣言 L で直接指定されたインターフェイスの一覧を C とします。
  • L に既に存在するインターフェイスの基本インターフェイスを L に追加します。
  • L から重複を削除します。
  • C から作成された可能な構築型が、型引数を L に置き換えた後、L の 2 つのインターフェイスが同一になる場合、C の宣言は無効です。 制約宣言は、構築可能なすべての型を判断する際に考慮されません。

: 上記 X クラス宣言では、インターフェイス リスト Ll<U>I<V> で構成されます。 U を持ち、V が同じ型である構築された型では、これら 2 つのインターフェイスが同一の型になるため、この宣言は無効です。 注釈

異なる継承レベルで指定されたインターフェイスが統合される場合があります。

interface I<T>
{
    void F();
}

class Base<U> : I<U>
{
    void I<U>.F() {...}
}

class Derived<U, V> : Base<U>, I<V> // Ok
{
    void I<V>.F() {...}
}

このコードは、Derived<U,V>I<U>I<V> の両方を実装している場合でも有効です。 コード

I<int> x = new Derived<int, int>();
x.F();

この場合は、Derived が効果的に Derived<int,int>' を再実装 (I<int>) するため、 でメソッドを呼び出します。

18.6.4 ジェネリック メソッドの実装

ジェネリック メソッドが暗黙的にインターフェースのメソッドを実装する場合、各メソッドの型パラメータに与えられる制約は、インターフェースの型パラメータが適切な型引数に置き換えられた後の両方の宣言において等価でなければなりません。メソッドの型パラメータは、左から右の順序で識別されます。

: 次のコード例では

interface I<X, Y, Z>
{
    void F<T>(T t) where T : X;
    void G<T>(T t) where T : Y;
    void H<T>(T t) where T : Z;
}

class C : I<object, C, string>
{
    public void F<T>(T t) {...}                  // Ok
    public void G<T>(T t) where T : C {...}      // Ok
    public void H<T>(T t) where T : string {...} // Error
}

メソッド C.F<T> は、I<object,C,string>.F<T> を暗黙的に実装しています。 この場合、C.F<T> はすべての型パラメーターに対する暗黙的な制約であるため、T: object は、制約 object を指定する必要はありません (また許可もされていません)。 このメソッド C.G<T> は、インターフェイスの型パラメーターが対応する型引数に置き換えられた後、制約がインターフェイス内のものと一致するため、I<object,C,string>.G<T> を暗黙的に実装します。 シール型 (この場合は C.H<T>) を制約として使用できないため、メソッド string の制約はエラーです。 暗黙的インターフェイス メソッドの実装の制約は一致する必要があるため、制約を省略してもエラーになります。 したがって、I<object,C,string>.H<T> を暗黙的に実装することはできません。 このインターフェイス メソッドは、明示的インターフェイス メンバーの実装を使用してのみ実装できます。

class C : I<object, C, string>
{
    ...
    public void H<U>(U u) where U : class {...}

    void I<object, C, string>.H<T>(T t)
    {
        string s = t; // Ok
        H<T>(t);
    }
}

この場合、明示的インターフェイス メンバーの実装は、より緩い制約を持つパブリック メソッドを呼び出します。 t から s への代入は有効です。これは、この制約がソース コードでは表現できない場合でも、TT: string の制約を継承するためです。 終了サンプル

: ジェネリック メソッドがインターフェイス メソッドを明示的に実装する場合、実装メソッドに制約は許可されません (§15.7.1§18.6.2)。 注釈

18.6.5 インターフェイスマッピング

クラスまたは構造体は、クラスまたは構造体の基底クラス リストにリストされているインターフェイスのすべてのメンバーの実装を提供する必要があります。 実装するクラスまたは構造体でインターフェイス メンバーの実装を検索するプロセスは、インターフェイス マッピングと呼ばれます。

クラスまたは構造体 C のインターフェイス マッピングは、C の基底クラス リストで指定された各インターフェイスの各メンバーの実装を特定します。 特定のインターフェイス メンバー I.M の実装 (ここで、I はメンバー M が宣言されているインターフェイス) は、各クラスまたは構造体 S を調べ、C から開始し、一致が見つかるまで C の後続の基底クラスごとに繰り返すことによって、決定されます。

  • S および I に一致する明示的なインターフェイス メンバー実装の宣言が M に含まれている場合、このメンバーは I.M の実装です。
  • それ以外の場合、S に、M と一致する非静的パブリック メンバーの宣言が含まれている場合、このメンバーは I.M の実装です。 複数のメンバーが一致する場合、どのメンバーが I.M の実装であるかは未指定です。 この状況は、S が、ジェネリック型で宣言された 2 つのメンバーが異なるシグネチャを持つが、型引数によってシグネチャが同一になる構築された型である場合にのみ、発生します。

C の基底クラス リストで指定されたすべてのインターフェイスのすべてのメンバーに対して実装を配置できない場合、コンパイル時エラーが発生します。 インターフェイスのメンバーには、基本インターフェイスから継承されたメンバーが含まれます。

構築されたインターフェイス型のメンバーは、§15.3.3 で指定されている対応する型引数に置き換えられた型パラメーターがあると見なされます。

: たとえば、次のジェネリック インターフェイス宣言の場合

interface I<T>
{
    T F(int x, T[,] y);
    T this[int y] { get; }
}

構築されたインターフェイス I<string[]> には、次のメンバーがあります。

string[] F(int x, string[,][] y);
string[] this[int y] { get; }

終了サンプル

インターフェイスのマッピングの目的上、クラスまたは構造体のメンバー A がインターフェイス メンバー B に一致するのは、次の条件を満たす場合です。

  • AB がメソッドであり、AB の名前、型、およびパラメーター リストが同一である。
  • AB がプロパティであり、AB の名前および型が同一で、AB のアクセサーが同一である場合 (A がインターフェイス メンバーの明示的な実装でない場合は、追加のアクセサーが許可されます)。
  • AB がイベントであり、AB の名前および型が同一である場合。
  • AB がインデクサーであり、AB の型およびパラメーター リストが同一で、AB のアクセサーが同一である (A がインターフェイス メンバーの明示的な実装でない場合は、追加のアクセサーが許可されます)。

インターフェイス マッピング アルゴリズムの主な影響を以下に挙げています。

  • インターフェイス メンバーを実装するクラスまたは構造体メンバーを決定する場合、明示的なインターフェイス メンバーの実装は、同じクラスまたは構造体内の他のメンバーよりも優先されます。
  • 非パブリック メンバーも静的メンバーも、インターフェイス マッピングには参加しません。

: 次のコード例の内容:

interface ICloneable
{
    object Clone();
}

class C : ICloneable
{
    object ICloneable.Clone() {...}
    public object Clone() {...}
}

明示的なインターフェイス メンバーの実装が他のメンバーよりも優先されるため、ICloneable.CloneC メンバーが CloneICloneable の実装になります。

終了サンプル

クラスまたは構造体が、同じ名前、型、およびパラメーター型のメンバーを含む 2 つ以上のインターフェイスを実装する場合、これらの各インターフェイス メンバーを 1 つのクラスまたは構造体メンバーにマップできます。

:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

class Page : IControl, IForm
{
    public void Paint() {...}
}

ここでは、PaintIControl の両方の IForm メソッドが、PaintPage メソッドにマップされます。 もちろん、2 つのメソッドに対して個別の明示的なインターフェイス メンバー実装を持つことも可能です。

終了サンプル

クラスまたは構造体が非表示のメンバーを含むインターフェイスを実装する場合、一部のメンバーは明示的インターフェイス メンバーの実装を通じて実装する必要があります。

:

interface IBase
{
    int P { get; }
}

interface IDerived : IBase
{
    new int P();
}

このインターフェイスの実装では、少なくとも 1 つの明示的インターフェイス メンバーの実装が必要で、次のいずれかの形式になります。

class C1 : IDerived
{
    int IBase.P { get; }
    int IDerived.P() {...}
}
class C2 : IDerived
{
    public int P { get; }
    int IDerived.P() {...}
}
class C3 : IDerived
{
    int IBase.P { get; }
    public int P() {...}
}

終了サンプル

クラスが同じ基本インターフェイスを持つ複数のインターフェイスを実装する場合、基本インターフェイスの実装は 1 つだけです。

: 次のコード例の内容:

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

class ComboBox : IControl, ITextBox, IListBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
    void IListBox.SetItems(string[] items) {...}
}

基底クラス リストで名前が指定された IControlIControl が継承する ITextBox、および IControl が継承する IListBox を個別に実装することはできません。 実際、これらのインターフェイスには別個の ID という概念はありません。 その代わり、ITextBoxIListBox の実装は IControl の同じ実装を共有し、ComboBox は単に、IControlITextBox、および IListBox の 3 つのインターフェイスを実装すると見なされます。

終了サンプル

基底クラスのメンバーは、インターフェイス マッピングに参加します。

: 次のコード例の内容:

interface Interface1
{
    void F();
}

class Class1
{
    public void F() {}
    public void G() {}
}

class Class2 : Class1, Interface1
{
    public new void G() {}
}

F のメソッド Class1 は、Class2'sInterface1 実装で使用されます。

終了サンプル

18.6.6 インターフェイス実装の継承

クラスは、基底クラスによって提供されるすべてのインターフェイス実装を継承します。

インターフェイスを明示的に再実装しないと、派生クラスは基底クラスから継承するインターフェイス マッピングを変更できません。

: 次の宣言では

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public void Paint() {...}
}

class TextBox : Control
{
    public new void Paint() {...}
}

PaintTextBox メソッドは PaintControl メソッドを非表示にしますが、Control.Paint への IControl.Paint のマッピングを変更せず、クラス インスタンスとインターフェイス インスタンスを介した Paint の呼び出しは、次の効果をもたらします

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();

終了サンプル

ただし、インターフェイス メソッドがクラス内の仮想メソッドにマップされている場合、派生クラスで仮想メソッドをオーバーライドし、インターフェイスの実装を変更することが可能です。

: 上記の宣言を次のように書き換えると

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public virtual void Paint() {...}
}

class TextBox : Control
{
    public override void Paint() {...}
}

次のように作用します

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes TextBox.Paint();

終了サンプル

明示的なインターフェイス メンバーの実装は仮想として宣言できないため、明示的なインターフェイス メンバーの実装をオーバーライドすることはできません。 ただし、明示的なインターフェイス メンバーの実装で別のメソッドを呼び出すことは有効で、その別のメソッドを仮想として宣言して、派生クラスによるオーバーライドを許可することができます。

:

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() { PaintControl(); }
    protected virtual void PaintControl() {...}
}

class TextBox : Control
{
    protected override void PaintControl() {...}
}

ここでは、Control から派生したクラスは、IControl.Paint メソッドをオーバーライドすることによって PaintControl の実装を特殊化できます。

終了サンプル

18.6.7 インターフェイスの再実装

インターフェイス実装を継承するクラスは、基底クラスの一覧に含めることでインターフェイスを再実装できます。

インターフェイスの再実装は、インターフェイスの初期実装とまったく同じインターフェイス マッピング規則に従います。 したがって、継承インターフェイス マッピングは、インターフェイスの再実装用に確立されたインターフェイス マッピングには何の影響も与えません。

: 次の宣言では

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() {...}
}

class MyControl : Control, IControl
{
    public void Paint() {}
}

ControlIControl.PaintControl.IControl.Paint にマップしても、MyControlIControl.Paint にマップする MyControl.Paint での再実装には影響しません。

終了サンプル

継承されたパブリック メンバー宣言と継承された明示的なインターフェイス メンバー宣言は、再実装されたインターフェイスのインターフェイス マッピング プロセスに参加します。

:

interface IMethods
{
    void F();
    void G();
    void H();
    void I();
}

class Base : IMethods
{
    void IMethods.F() {}
    void IMethods.G() {}
    public void H() {}
    public void I() {}
}

class Derived : Base, IMethods
{
    public void F() {}
    void IMethods.H() {}
}

この場合、IMethods での Derived の実装により、インターフェイス メソッドが Derived.FBase.IMethods.GDerived.IMethods.H、および Base.I にマップされます。

終了サンプル

クラスがインターフェイスを実装すると、そのインターフェイスのすべての基本インターフェイスも暗黙的に実装されます。 同様に、インターフェイスの再実装も暗黙的に、インターフェイスのすべての基本インターフェイスの再実装となります。

:

interface IBase
{
    void F();
}

interface IDerived : IBase
{
    void G();
}

class C : IDerived
{
    void IBase.F() {...}
    void IDerived.G() {...}
}

class D : C, IDerived
{
    public void F() {...}
    public void G() {...}
}

この場合、IDerived の再実装は、IBase も再実装し、IBase.FD.F にマッピングします。

終了サンプル

18.6.8 抽象クラスとインターフェイス

抽象クラス以外のクラスと同様に、抽象クラスは、クラスの基底クラス リストにリストされているインターフェイスのすべてのメンバーの実装を提供する必要があります。 ただし、抽象クラスでは、インターフェイス メソッドを抽象メソッドにマップすることが許可されています。

:

interface IMethods
{
    void F();
    void G();
}

abstract class C : IMethods
{
    public abstract void F();
    public abstract void G();
    }

ここでは、IMethods の実装は、FG を抽象メソッドにマップします。これは、C から派生する非抽象クラスでオーバーライドされる必要があります。

終了サンプル

明示的なインターフェイス メンバーの実装を抽象にすることはできませんが、明示的なインターフェイス メンバーの実装は抽象メソッドを呼び出すことが当然ながら許可されています。

:

interface IMethods
{
    void F();
    void G();
}

abstract class C: IMethods
{
    void IMethods.F() { FF(); }
    void IMethods.G() { GG(); }
    protected abstract void FF();
    protected abstract void GG();
}

この場合は、C から派生する非抽象クラスは、FFGG をオーバーライドする必要があり、これにより IMethods の実際の実装が提供されます。

終了サンプル