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は、オプションの一連の attributes (§22) の後に、オプションのinterface_modifierセット (§18.2. 2)、オプションの部分修飾子 (§15.2.7)、その後にキーワード interface
とインターフェイスに名前を付けた identifier が続きます。 オプションのvariant_type_parameter_list仕様 (§18.2.3) の後に省略可能なinterface_basespecification (§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 で説明されているように、インターフェイスが継承されたメンバーを同じ名前で非表示にすることを指定。
public
、protected
、internal
、およびprivate
修飾子は、インターフェイスのアクセシビリティを制御します。 インターフェイス宣言が行われるコンテキストによっては、これらの修飾子の一部のみが許可される場合があります (§7.5.2)。 部分型宣言 (§15.2.7) にアクセシビリティ仕様 ( public
、 protected
、 internal
、および private
修飾子を使用) が含まれている場合、 §15.2.2 の規則 適用されます。
18.2.3 バリアント型パラメーター リスト
18.2.3.1 全般
バリアント型パラメーター リストは、インターフェイス型とデリゲート型でのみ実行できます。 通常の type_parameter_listとの違いは、各型パラメーターの省略可能な variance_annotation です。
variant_type_parameter_list
: '<' variant_type_parameters '>'
;
variant_type_parameters
: attributes? variance_annotation? type_parameter
| variant_type_parameters ',' attributes? variance_annotation?
type_parameter
;
variance_annotation
: 'in'
| 'out'
;
分散注釈が out
の場合、型パラメーターは 共変と言われます。 分散注釈が in
場合、型パラメーターは contravariant と言われます。 分散注釈がない場合、型パラメーターは 不変と言われます。
例: 次の例を参照してください。
interface C<out X, in Y, Z> { X M(Y y); Z P { get; set; } }
X
は共変であり、Y
は反変であり、Z
は不変です。end の例
ジェネリック インターフェイスが複数の部分で宣言されている場合 (§15.2.3)、各部分宣言では、各型パラメーターに同じ分散を指定する必要があります。
18.2.3.2 分散安全性
型の型パラメーター リストに分散注釈が出現すると、型宣言内で型が発生する可能性がある場所が制限されます。
型 T は output-unsafe です 次のいずれかが保持されている場合。
T
は反変型パラメーターですT
は、出力アンセーフ要素型を持つ配列型ですT
は、ジェネリック型から構築Sᵢ,... Aₑ
インターフェイスまたはデリゲート型S<Xᵢ, ... Xₑ>
で、次のいずれかのAᵢ
少なくとも 1 つを保持します。Xᵢ
は共変または不変であり、Aᵢ
は出力安全ではありません。Xᵢ
は反変または不変であり、Aᵢ
は入力セーフではありません。
T 型は input-unsafe 次のいずれかが保持されている場合です。
T
は共変型パラメーターですT
は、入力アンセーフ要素型を持つ配列型ですT
は、ジェネリック型から構築S<Aᵢ,... Aₑ>
インターフェイスまたはデリゲート型S<Xᵢ, ... Xₑ>
で、次のいずれかのAᵢ
少なくとも 1 つを保持します。Xᵢ
は共変または不変であり、Aᵢ
は入力セーフではありません。Xᵢ
は反変または不変であり、Aᵢ
は出力安全ではありません。
直感的に、出力アンセーフ型は出力位置では禁止され、入力の安全でない型は入力位置で禁止されます。
型は出力セーフでない場合は output-safe 、input-unsafe でない場合は input-safe です。
18.2.3.3 分散変換
分散注釈の目的は、インターフェイス型とデリゲート型に対してより寛大な (ただし、型セーフな) 変換を提供することです。 このために、暗黙的な (§10.2) と明示的な変換 (§10.3) の定義では、分散変換の概念が使用されます。これは次のように定義されています。
型T<Aᵢ, ..., Aᵥ>
は、T
がバリアント型パラメーターT<Xᵢ, ..., Xᵥ>
で宣言されたインターフェイスまたはデリゲート型のいずれかである場合、型T<Bᵢ, ..., Bᵥ>
に対して分散変換可能であり、各バリアント型パラメーターXᵢ
次のいずれかを保持します。
Xᵢ
は共変であり、暗黙的な参照または ID 変換がAᵢ
からBᵢ
Xᵢ
は反変であり、暗黙的な参照または ID 変換がBᵢ
からAᵢ
Xᵢ
は不変であり、id 変換はAᵢ
からBᵢ
18.2.4 基本インターフェイス
インターフェイスは、インターフェイスの explicit 基本インターフェイス と呼ばれる、0 個以上のインターフェイス型から継承できます。 インターフェイスに 1 つ以上の明示的な基本インターフェイスがある場合、そのインターフェイスの宣言では、インターフェイス識別子の後に、基本インターフェイス型のコロンとコンマ区切りのリストが続きます。
interface_base
: ':' interface_type_list
;
明示的な基本インターフェイスは、インターフェイス型 (§8.4、 §18.2) で構築できます。 基本インターフェイスは単独では型パラメーターにすることはできませんが、スコープ内の型パラメーターを含めることができます。
構築されたインターフェイス型の場合、明示的な基本インターフェイスは、ジェネリック型宣言で明示的な基本インターフェイス宣言を受け取り、基本インターフェイス宣言の各 type_parameter に対して、構築された型の対応する type_argument を置き換えることによって形成されます。
インターフェイスの明示的な基本インターフェイスは、少なくともインターフェイス自体と同じくらいアクセス可能である必要があります (§7.5.5)。
注: たとえば、
public
インターフェイスのinterface_baseでprivate
またはinternal
インターフェイスを指定するのはコンパイル時エラーです。 end note
インターフェイスが直接または間接的にそれ自体から継承するのはコンパイル時エラーです。
インターフェイスの ベース インターフェイス は、明示的な基本インターフェイスとその基本インターフェイスです。 つまり、基本インターフェイスのセットは、明示的な基本インターフェイス、明示的な基本インターフェイスなどの完全な推移的なクロージャです。 インターフェイスは、その基本インターフェイスのすべてのメンバーを継承します。
例: 次のコード内
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}
IComboBox
の基本インターフェイスは、IControl
、ITextBox
、およびIListBox
です。 言い換えると、上記のIComboBox
インターフェイスは、メンバーSetText
とSetItems
とPaint
を継承します。end の例
構築されたジェネリック型から継承されたメンバーは、型の置換後に継承されます。 つまり、メンバー内のすべての構成要素型には、基底クラス宣言の型パラメーターが、 class_base 仕様で使用される対応する型引数に置き換えられます。
例: 次のコード内
interface IBase<T> { T[] Combine(T a, T b); } interface IDerived : IBase<string[,]> { // Inherited: string[][,] Combine(string[,] a, string[,] b); }
インターフェイス
IDerived
は、型パラメーターT
がstring[,]
に置き換えられた後、Combine
メソッドを継承します。end の例
インターフェイスを実装するクラスまたは構造体は、インターフェイスのすべての基本インターフェイスも暗黙的に実装します。
部分インターフェイス宣言 (§15.2.7) の複数の部分でのインターフェイスの処理については、 §15.2.4.3 で詳しく説明します。
インターフェイスのすべての基本インターフェイスは、出力セーフである必要があります (§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 つのメソッドには、
in
、out
、およびref
によってのみ異なるシグネチャは含まれません。 - プロパティまたはイベントの名前は、同じインターフェイスで宣言されている他のすべてのメンバーの名前とは異なる必要があります。
- インデクサーのシグネチャは、同じインターフェイスで宣言されている他のすべてのインデクサーのシグネチャとは異なる必要があります。
インターフェイスの継承されたメンバーは、特にインターフェイスの宣言空間の一部ではありません。 したがって、インターフェイスは、継承されたメンバーと同じ名前またはシグネチャを持つメンバーを宣言できます。 これが発生すると、派生インターフェイス メンバーは、基本インターフェイス メンバー再表示と言われます。 継承されたメンバーを非表示にしてもエラーとは見なされませんが、コンパイラによって警告が発行されます。 警告を抑制するために、派生インターフェイス メンバーの宣言には、派生メンバーが基本メンバーを非表示にすることを意図していることを示す new
修飾子を含める必要があります。 このトピックについては、 §7.7.2.3 で詳しく説明します。
継承されたメンバーを非表示にしない宣言に new
修飾子が含まれている場合、その結果に対して警告が発行されます。 この警告は、 new
修飾子を削除することで抑制されます。
注: クラス
object
のメンバーは、厳密には、どのインターフェイス (§18.4) のメンバーでありません。 ただし、クラスobject
のメンバーは、任意のインターフェイス型 (§12.5) のメンバー参照を介して使用できます。 end note
複数の部分で宣言されたインターフェイスのメンバーのセット (§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* ';'
;
インターフェイス メソッド宣言の attributes、 return_type、 ref_return_type、 identifier、および parameter_list は、クラス内のメソッド宣言と同じ意味を持ちます (§15.6)。 インターフェイス メソッド宣言はメソッド本体を指定することは許可されていないため、宣言は常にセミコロンで終わります。
インターフェイス メソッドのすべてのパラメーター型は、入力セーフ (§18.2.3.2) で、戻り値の型は void
または出力セーフである必要があります。 さらに、出力または参照パラメーターの型も出力セーフである必要があります。
注: 出力パラメーターは、一般的な実装制限により、入力セーフである必要があります。 end note
さらに、メソッドの任意の型パラメーターに対する各クラス型制約、インターフェイス型制約、および型パラメーター制約は、入力セーフである必要があります。
さらに、メソッドの任意の型パラメーターに対する各クラス型制約、インターフェイス型制約、および型パラメーター制約は、入力セーフである必要があります。
これらの規則により、インターフェイスの共変または反変の使用が確実にタイプ セーフのままになります。
例:
interface I<out T> { void M<U>() where U : T; // Error }
は、
U
の型パラメーター制約としてT
を使用することは、入力セーフではないため、形式が正しくありません。この制限が適用されていない場合、次の方法でタイプ セーフに違反する可能性があります。
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>
の呼び出しです。 しかし、その呼び出しではE
D
から派生している必要があるため、ここでは型の安全性に違反します。end の例
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' ';'
;
インターフェイス プロパティ宣言の attributes、 type、および identifier は、クラス (§15.7) のプロパティ宣言と同じ意味を持ちます。
インターフェイス プロパティ宣言のアクセサーは、クラス プロパティ宣言 (§15.7.3) のアクセサーに対応します。ただし、 accessor_body は常にセミコロンにする必要があります。 したがって、アクセサーは、プロパティが読み取り/書き込み、読み取り専用、または書き込み専用のいずれであるかを示すだけです。
インターフェイス プロパティの型は、get アクセサーがある場合は出力セーフであり、set アクセサーがある場合は入力セーフである必要があります。
18.4.4 インターフェイス イベント
インターフェイス イベントは、 interface_event_declarationを使用して宣言されます。
interface_event_declaration
: attributes? 'new'? 'event' type identifier ';'
;
インターフェイス イベント宣言の attributes、 type、および identifier は、クラス (§15.8) のイベント宣言と同じ意味を持ちます。
インターフェイス イベントの型は、入力セーフである必要があります。
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 '}'
;
インターフェイス インデクサー宣言の attributes、 type、および parameter_list は、クラス (§15.9) のインデクサー宣言と同じ意味を持ちます。
インターフェイス インデクサー宣言のアクセサーは、クラス インデクサー宣言 (§15.9) のアクセサーに対応します。ただし、 accessor_body は常にセミコロンにする必要があります。 したがって、アクセサーは、インデクサーが読み取り/書き込み、読み取り専用、または書き込み専用のいずれであるかを単に示します。
インターフェイス インデクサーのすべてのパラメーター型は、入力セーフである必要があります (§18.2.3.2)。 さらに、出力または参照パラメーターの型も出力セーフである必要があります。
注: 出力パラメーターは、一般的な実装制限により、入力セーフである必要があります。 end note
インターフェイス インデクサーの型は、get アクセサーがある場合は出力セーフであり、set アクセサーがある場合は入力セーフである必要があります。
18.4.6 インターフェイス メンバー アクセス
インターフェイス メンバーは、フォーム I.M
およびI[A]
のメンバー アクセス (§12.8.7) とインデクサー アクセス (§12.8.11.3) 式によってアクセスされます。I
はインターフェイス型であり、M
はそのインターフェイス型のメソッド、プロパティ、またはイベントであり、A
はインデクサー引数リストです。
厳密に単一継承であるインターフェイスの場合 (継承チェーン内の各インターフェイスには、0 個または 1 つの直接基本インターフェイスがあります)、メンバー参照 (§12.5)、メソッド呼び出し (§12.8.9.9) の影響 2)、インデクサー アクセス (§12.8.11.3) ルールは、クラスや構造体の場合とまったく同じです。派生メンバーが多いほど、同じ名前またはシグネチャを持つ派生メンバーが非表示になります。 ただし、複数継承インターフェイスの場合、関連のない 2 つ以上の基本インターフェイスが同じ名前またはシグネチャを持つメンバーを宣言すると、あいまいさが発生する可能性があります。 このサブクラウスは、いくつかの例を示しています。その一部はあいまいになり、それ以外の例はあいまいになります。 いずれの場合も、明示的なキャストを使用してあいまいさを解決できます。
例: 次のコード内
interface IList { int Count { get; set; } } interface ICounter { void Count(int i); } interface IListCounter : IList, ICounter {} class C { void Test(IListCounter x) { x.Count(1); // Error x.Count = 1; // Error ((IList)x).Count = 1; // Ok, invokes IList.Count.set ((ICounter)x).Count(1); // Ok, invokes ICounter.Count } }
最初の 2 つのステートメントでは、
IListCounter
のCount
のメンバー参照 (§12.5) があいまいであるため、コンパイル時エラーが発生します。 例で示すように、あいまいさは、適切な基本インターフェイス型にx
をキャストすることによって解決されます。 このようなキャストには実行時コストはありません。コンパイル時にインスタンスを派生型として表示するだけで構成されます。end の例
例: 次のコード内
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)
は、§12.6.4 のオーバーロード解決規則を適用してIInteger.Add
を選択します。 同様に、呼び出しn.Add(1.0)
はIDouble.Add
を選択します。 明示的なキャストが挿入されると、候補メソッドは 1 つしか存在しないため、あいまいさはありません。end の例
例: 次のコード内
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.F
は、IRight
を経由するアクセス パスで非表示にされていないように見えます。複数継承インターフェイスで非表示にするための直感的なルールは、単純に次のとおりです。メンバーが任意のアクセス パスで非表示の場合、すべてのアクセス パスで非表示になります。
IDerived
からILeft
IBase
へのアクセス パスはIBase.F
を非表示にするため、IDerived
からIRight
からIBase
へのアクセス パスでもメンバーは非表示になります。end の例
18.5 修飾インターフェイス メンバー名
インターフェイス メンバーは、 修飾インターフェイス メンバー名によって参照されることがあります。 インターフェイス メンバーの修飾名は、メンバーが宣言されているインターフェイスの名前の後にドットが続き、その後にメンバーの名前が続きます。 メンバーの修飾名は、メンバーが宣言されているインターフェイスを参照します。
例: 宣言を指定する
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); }
Paint
の修飾名がIControl.Paint
され、SetText の修飾名がITextBox.SetText
。 上記の例では、ITextBox.Paint
としてPaint
を参照することはできません。end の例
インターフェイスが名前空間の一部である場合、修飾インターフェイス メンバー名に名前空間名を含めることができます。
例:
namespace System { public interface ICloneable { object Clone(); } }
System
名前空間内では、ICloneable.Clone
とSystem.ICloneable.Clone
の両方が、Clone
メソッドの修飾インターフェイス メンバー名です。end の例
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) {...} }
end の例
インターフェイスを直接実装するクラスまたは構造体は、インターフェイスのすべての基本インターフェイスも暗黙的に実装します。 これは、クラスまたは構造体が基底クラスリスト内のすべての基本インターフェイスを明示的に一覧表示しない場合でも当てはまります。
例:
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { public void Paint() {...} public void SetText(string text) {...} }
ここでは、クラス
TextBox
はIControl
とITextBox
の両方を実装します。end の例
クラス 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> {}
end の例
ジェネリック クラス宣言の基本インターフェイスは、 §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>.this
とIDictionary<int,T>.Add
を示します。end の例
例: 場合によっては、インターフェイス メンバーの名前が実装クラスに適していない場合があります。その場合、インターフェイス メンバーは明示的なインターフェイス メンバーの実装を使用して実装できます。 たとえば、ファイル抽象化を実装するクラスは、ファイル リソースを解放する効果がある
Close
メンバー関数を実装し、明示的なインターフェイス メンバー実装を使用してIDisposable
インターフェイスのDispose
メソッドを実装する可能性があります。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); } }
end の例
メソッド呼び出し、プロパティ アクセス、イベント アクセス、またはインデクサー アクセスでは、修飾インターフェイス メンバー名を使用して明示的なインターフェイス メンバーの実装にアクセスすることはできません。 明示的なインターフェイス メンバーの実装は、インターフェイス インスタンスを介してのみアクセスでき、その場合はメンバー名だけで参照されます。
明示的なインターフェイス メンバーの実装では、extern
またはasync
以外の修飾子 (§15.6) を含めるコンパイル時エラーです。
type_parameter_constraints_clauseを含める明示的なインターフェイス メソッドの実装のコンパイル時エラーです。 ジェネリック明示的インターフェイス メソッドの実装の制約は、インターフェイス メソッドから継承されます。
注: 明示的なインターフェイス メンバーの実装には、他のメンバーとは異なるアクセシビリティ特性があります。 明示的なインターフェイス メンバーの実装は、メソッド呼び出しまたはプロパティ アクセスで修飾インターフェイス メンバー名を介してアクセスすることはないため、ある意味プライベートです。 ただし、インターフェイスを介してアクセスできるため、宣言されているインターフェイスと同じ意味でもパブリックになります。 明示的なインターフェイス メンバーの実装は、次の 2 つの主な目的を果たします。
- 明示的なインターフェイス メンバーの実装はクラスまたは構造体インスタンスを介してアクセスできないため、インターフェイス実装をクラスまたは構造体のパブリック インターフェイスから除外できます。 これは、クラスまたは構造体が、そのクラスまたは構造体のコンシューマーにとって関心のない内部インターフェイスを実装する場合に特に便利です。
- 明示的なインターフェイス メンバー実装では、同じシグネチャを持つインターフェイス メンバーのあいまいさを解消できます。 明示的なインターフェイス メンバーの実装がないと、クラスまたは構造体が同じシグネチャと戻り値の型を持つインターフェイス メンバーの異なる実装を持つことは不可能です。これは、クラスまたは構造体が同じシグネチャを持ち、異なる戻り値の型を持つすべてのインターフェイス メンバーで実装を持つことは不可能です。
end note
明示的なインターフェイス メンバーの実装を有効にするには、クラスまたは構造体は、修飾インターフェイス メンバー名、型、型パラメーターの数、およびパラメーター型が明示的なインターフェイス メンバー実装のメンバーと正確に一致するメンバーを含む基底クラス リスト内のインターフェイスに名前を付ける必要があります。 インターフェイス関数メンバーにパラメーター配列がある場合、関連付けられている明示的なインターフェイス メンバー実装の対応するパラメーターは、 params
修飾子を持つことが許可されますが、必須ではありません。 インターフェイス関数メンバーにパラメーター配列がない場合、関連付けられている明示的なインターフェイス メンバーの実装には、パラメーター配列は含まれません。
例: したがって、次のクラスでは
class Shape : ICloneable { object ICloneable.Clone() {...} int IComparable.CompareTo(object other) {...} // invalid }
IComparable.CompareTo
の宣言では、IComparable
がShape
の基底クラスの一覧にリストされておらず、ICloneable
の基本インターフェイスではないため、コンパイル時エラーが発生します。 同様に、宣言ではclass Shape : ICloneable { object ICloneable.Clone() {...} } class Ellipse : Shape { object ICloneable.Clone() {...} // invalid }
Ellipse
の基底クラスリストにICloneable
が明示的にリストされていないため、Ellipse
のICloneable.Clone
の宣言によってコンパイル時エラーが発生します。end の例
明示的なインターフェイス メンバー実装の修飾インターフェイス メンバー名は、メンバーが宣言されたインターフェイスを参照する必要があります。
例: したがって、宣言内
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} }
Paint の明示的なインターフェイス メンバーの実装は、
ITextBox.Paint
ではなく、IControl.Paint
として記述する必要があります。end の例
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();
end の例
ジェネリック型宣言のインターフェイス リストが有効かどうかを判断するには、次の手順を実行します。
- ジェネリック クラス、構造体、またはインターフェイス宣言
C
で直接指定されたインターフェイスの一覧L
します。 L
に既に存在するインターフェイスの基本インターフェイスをL
に追加します。L
から重複を削除します。C
から作成された可能な構築型が、型引数をL
に置き換えた後、L
の 2 つのインターフェイスが同一になる場合、C
の宣言は無効です。 構築可能なすべての型を決定する場合、制約宣言は考慮されません。
注: 上記
X
クラス宣言では、インターフェイス リストL
はl<U>
とI<V>
で構成されます。U
を持ち、V
が同じ型である構築された型では、これら 2 つのインターフェイスが同じ型になるため、宣言は無効です。 end note
異なる継承レベルで指定されたインターフェイスを統合できます。
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();
はI<int>
を効果的に再実装Derived<int,int>'
(§18.6.7) ため、Derived
でメソッドを呼び出します。
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>
を実装します。 この場合、object
はすべての型パラメーターに対する暗黙的な制約であるため、制約T: object
を指定するためにC.F<T>
は必要ありません (また許可されていません)。 このメソッドC.G<T>
、インターフェイスの型パラメーターが対応する型引数に置き換えられた後、制約がインターフェイス内のものと一致するため、I<object,C,string>.G<T>
を暗黙的に実装します。 シール型 (この場合はstring
) を制約として使用できないため、メソッドC.H<T>
の制約はエラーです。 暗黙的なインターフェイス メソッドの実装の制約は一致する必要があるため、制約を省略してもエラーになります。 したがって、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 への割り当ては有効です。この制約はソース コードでは表現できない場合でも、
T
はT: string
の制約を継承するためです。 end の例
注: ジェネリック メソッドがインターフェイス メソッドを明示的に実装する場合、実装メソッドに制約は許可されません (§15.7.1、 §18.6.2)。 end note
18.6.5 インターフェイスマッピング
クラスまたは構造体は、クラスまたは構造体の基底クラスリストにリストされているインターフェイスのすべてのメンバーの実装を提供する必要があります。 実装するクラスまたは構造体でインターフェイス メンバーの実装を検索するプロセスは、 インターフェイス マッピングと呼ばれます。
クラスまたは構造体のインターフェイス マッピング C
、 C
の基底クラスの一覧で指定された各インターフェイスの各メンバーの実装を検索します。 特定のインターフェイス メンバー I.M
の実装 ( I
はメンバー M
が宣言されているインターフェイス) は、各クラスまたは構造体の S
を調べて、 C
から始まり、一致が見つかるまで C
の後続の基底クラスごとに繰り返すことによって決定されます。
S
I
およびM
に一致する明示的なインターフェイス メンバー実装の宣言が含まれている場合、このメンバーはI.M
の実装です。- それ以外の場合、
S
M
に一致する非静的パブリック メンバーの宣言が含まれている場合、このメンバーはI.M
の実装になります。 複数のメンバーが一致する場合、どのメンバーがI.M
の実装であるかは指定されていません。 この状況は、ジェネリック型で宣言された 2 つのメンバーが異なるシグネチャを持つが、型引数によってシグネチャが同一になる、S
が構築された型である場合にのみ発生します。
コンパイル時エラーは、 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; }
end の例
インターフェイス マッピングのために、クラスまたは構造体メンバー A
は、次の場合に B
インターフェイス メンバーと一致します。
A
B
はメソッドであり、A
とB
の名前、型、およびパラメーター リストは同じです。A
B
はプロパティであり、A
とB
の名前と型は同じであり、A
はB
と同じアクセサーを持ちます (明示的なインターフェイス メンバー実装でない場合、A
は追加のアクセサーを持つことができます)。A
B
はイベントであり、A
とB
の名前と種類は同じです。A
B
はインデクサーであり、A
とB
の型とパラメーターのリストは同じであり、A
はB
と同じアクセサーを持ちます (明示的なインターフェイス メンバー実装でない場合、A
は追加のアクセサーを持つことができます)。
インターフェイス マッピング アルゴリズムの主な影響は次のとおりです。
- インターフェイス メンバーを実装するクラスまたは構造体メンバーを決定する場合、明示的なインターフェイス メンバーの実装は、同じクラスまたは構造体内の他のメンバーよりも優先されます。
- 非パブリック メンバーも静的メンバーも、インターフェイス マッピングには参加しません。
例: 次のコード内
interface ICloneable { object Clone(); } class C : ICloneable { object ICloneable.Clone() {...} public object Clone() {...} }
明示的なインターフェイス メンバーの実装が他のメンバーよりも優先されるため、
C
のICloneable.Clone
メンバーが 'ICloneable' のClone
の実装になります。end の例
クラスまたは構造体が、同じ名前、型、およびパラメーター型のメンバーを含む 2 つ以上のインターフェイスを実装する場合、これらの各インターフェイス メンバーを 1 つのクラスまたは構造体メンバーにマップできます。
例:
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }
ここでは、
IControl
とIForm
の両方のPaint
メソッドが、Page
のPaint
メソッドにマップされます。 もちろん、2 つのメソッドに対して個別の明示的なインターフェイス メンバー実装を持つことも可能です。end の例
クラスまたは構造体が非表示のメンバーを含むインターフェイスを実装する場合、一部のメンバーは明示的なインターフェイス メンバーの実装を通じて実装する必要があります。
例:
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() {...} }
end の例
クラスが同じ基本インターフェイスを持つ複数のインターフェイスを実装する場合、基本インターフェイスの実装は 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) {...} }
基底クラスの一覧で名前が付けられた
IControl
、ITextBox
によって継承されたIControl
、およびIListBox
によって継承されたIControl
を個別に実装することはできません。 実際、これらのインターフェイスに別の ID という概念はありません。 むしろ、ITextBox
とIListBox
の実装はIControl
の同じ実装を共有し、ComboBox
は単に、IControl
、ITextBox
、およびIListBox
の 3 つのインターフェイスを実装すると見なされます。end の例
基底クラスのメンバーは、インターフェイス マッピングに参加します。
例: 次のコード内
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }
Class1
のF
メソッドは、Interface1
の実装Class2's
使用されます。end の例
18.6.6 インターフェイス実装の継承
クラスは、基底クラスによって提供されるすべてのインターフェイス実装を継承します。
インターフェイスを明示的に再実装しないと、派生クラスは基底クラスから継承するインターフェイス マッピングを変更できません。
例: 宣言内
interface IControl { void Paint(); } class Control : IControl { public void Paint() {...} } class TextBox : Control { public new void Paint() {...} }
TextBox
のPaint
メソッドはControl
のPaint
メソッドを隠しますが、IControl.Paint
へのControl.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();
end の例
ただし、インターフェイス メソッドがクラス内の仮想メソッドにマップされている場合、派生クラスで仮想メソッドをオーバーライドし、インターフェイスの実装を変更することができます。
例: 上記の宣言の書き換え
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();
end の例
明示的なインターフェイス メンバーの実装は仮想として宣言できないため、明示的なインターフェイス メンバーの実装をオーバーライドすることはできません。 ただし、明示的なインターフェイス メンバーの実装で別のメソッドを呼び出すには完全に有効です。また、その他のメソッドを仮想として宣言して、派生クラスがオーバーライドできるようにします。
例:
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() { PaintControl(); } protected virtual void PaintControl() {...} } class TextBox : Control { protected override void PaintControl() {...} }
ここでは、
Control
から派生したクラスは、PaintControl
メソッドをオーバーライドすることによってIControl.Paint
の実装を特殊化できます。end の例
18.6.7 インターフェイスの再実装
インターフェイス実装を継承するクラスは、基底クラスの一覧に含めることでインターフェイスを再実装できます。
インターフェイスの再実装は、インターフェイスの初期実装とまったく同じインターフェイス マッピング規則に従います。 したがって、継承されたインターフェイス マッピングは、インターフェイスの再実装用に確立されたインターフェイス マッピングには何の影響もありません。
例: 宣言内
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() {...} } class MyControl : Control, IControl { public void Paint() {} }
Control
IControl.Paint
をControl.IControl.Paint
にマップしても、IControl.Paint
をMyControl.Paint
にマップするMyControl
での再実装には影響しません。end の例
継承されたパブリック メンバー宣言と継承された明示的なインターフェイス メンバー宣言は、再実装されたインターフェイスのインターフェイス マッピング プロセスに参加します。
例:
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() {} }
ここでは、
Derived
でのIMethods
の実装により、インターフェイス メソッドがDerived.F
、Base.IMethods.G
、Derived.IMethods.H
、およびBase.I
にマップされます。end の例
クラスがインターフェイスを実装すると、そのインターフェイスのすべての基本インターフェイスも暗黙的に実装されます。 同様に、インターフェイスの再実装も暗黙的に、インターフェイスのすべての基本インターフェイスの再実装です。
例:
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.F
をD.F
にマッピングします。end の例
18.6.8 抽象クラスとインターフェイス
抽象クラス以外のクラスと同様に、抽象クラスは、クラスの基底クラスリストにリストされているインターフェイスのすべてのメンバーの実装を提供する必要があります。 ただし、抽象クラスでは、インターフェイス メソッドを抽象メソッドにマップできます。
例:
interface IMethods { void F(); void G(); } abstract class C : IMethods { public abstract void F(); public abstract void G(); }
ここでは、
IMethods
の実装では、F
とG
を抽象メソッドにマップします。これは、C
から派生する非抽象クラスでオーバーライドされます。end の例
明示的なインターフェイス メンバーの実装を抽象にすることはできませんが、明示的なインターフェイス メンバーの実装は抽象メソッドを呼び出すことがもちろん許可されています。
例:
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
から派生する非抽象クラスは、FF
とGG
をオーバーライドして、IMethods
の実際の実装を提供する必要があります。end の例
ECMA C# draft specification