C#6.0语言规范(十三) 接口
接口定义合同。实现接口的类或结构必须遵守其合同。接口可以从多个基接口继承,并且类或结构可以实现多个接口。
接口可以包含方法,属性,事件和索引器。接口本身不为它定义的成员提供实现。接口仅指定必须由实现接口的类或结构提供的成员。
接口声明
一个interface_declaration是type_declaration(类型声明,声明新的接口类型)。
1 interface_declaration 2 : attributes? interface_modifier* 'partial'? 'interface' 3 identifier variant_type_parameter_list? interface_base? 4 type_parameter_constraints_clause* interface_body ';'? 5 ;
一个interface_declaration包括一组可选的属性(属性),随后一组可选的interface_modifier S(接口改性剂),随后任选的partial
改性剂,接着由关键字interface
和标识符名称的接口处,接着任选的variant_type_parameter_list规范(变量类型参数列表),后跟可选的interface_base规范(Base接口),后跟可选的type_parameter_constraints_clause规范(键入参数约束),然后是interface_body(接口主体),可选地后跟分号。
接口修饰符
一个interface_declaration可任选地包括接口改性剂的一个序列:
1 interface_modifier 2 : 'new' 3 | 'public' 4 | 'protected' 5 | 'internal' 6 | 'private' 7 | interface_modifier_unsafe 8 ;
同一修饰符在接口声明中多次出现是编译时错误。
所述new
改性剂只允许在一个类中定义的接口。它指定接口使用相同的名称隐藏继承的成员,如“新修饰符”中所述。
在public
,protected
,internal
,和private
修饰符控制接口的可访问性。根据接口声明发生的上下文,可能只允许其中一些修饰符(声明可访问性)。
部分编辑
所述partial
改性剂表明此interface_declaration是部分类型声明。在封闭命名空间或类型声明中具有相同名称的多个部分接口声明组合在一起,形成一个接口声明,遵循在部分类型中指定的规则。
变体类型参数列表
变体类型参数列表只能出现在接口和委托类型上。与普通type_parameter_list s 的区别在于每个类型参数的可选variance_annotation。
1 variant_type_parameter_list 2 : '<' variant_type_parameters '>' 3 ; 4 5 variant_type_parameters 6 : attributes? variance_annotation? type_parameter 7 | variant_type_parameters ',' attributes? variance_annotation? type_parameter 8 ; 9 10 variance_annotation 11 : 'in' 12 | 'out' 13 ;
如果方差注释是out
,则称类型参数是协变的。如果方差注释是in
,则称类型参数是逆变的。如果没有方差注释,则称类型参数是不变的。
在这个例子中
1 interface C<out X, in Y, Z> 2 { 3 X M(Y y); 4 Z P { get; set; } 5 }
X
是协变的,Y
是逆变的,Z
是不变的。
方差安全
类型的类型参数列表中出现方差注释会限制类型声明中可能出现类型的位置。
A型T
是输出不安全如果下列之一成立:
T
是逆变型参数T
是一个具有输出不安全元素类型的数组类型T
是一种S<A1,...,Ak>
从泛型类型构造的接口或委托类型,S<X1,...,Xk>
其中至少有Ai
以下一种类型:Xi
是协变的或不变的,Ai
输出不安全。Xi
是逆变或不变的,Ai
输入安全。
A型T
是输入不安全的,如果下列之一成立:
T
是一个协变类型参数T
是一个具有input-unsafe元素类型的数组类型T
是一种S<A1,...,Ak>
从泛型类型构造的接口或委托类型,S<X1,...,Xk>
其中至少有Ai
以下一种类型:Xi
是协变的或不变的,Ai
输入不安全。Xi
是逆变的或不变的,Ai
输出不安全。
直观地,在输出位置禁止输出不安全类型,并且在输入位置禁止输入不安全类型。
A型输出安全的,如果它不是输出不安全,并输入安全的,如果它不是输入不安全的。
方差转换
方差注释的目的是为接口和委托类型提供更宽松(但仍然类型安全)的转换。为此,隐式(隐式转换)和显式转换(显式转换)的定义使用了方差 - 可转换性的概念,其定义如下:
A型T<A1,...,An>
是方差-转换为一个类型T<B1,...,Bn>
是否T
是一个接口或与变体类型参数声明的委托类型T<X1,...,Xn>
,并且对于每个变体类型参数Xi
的下面保持一个:
Xi
是协变和隐式引用或标识转换从存在Ai
于Bi
Xi
是逆变和隐式引用或标识转换从存在Bi
于Ai
Xi
是不变的,并且一个标识转换从存在Ai
于Bi
基础接口
接口可以从零个或多个接口类型继承,这些接口类型称为接口的显式基接口。当接口具有一个或多个显式基接口时,则在该接口的声明中,接口标识符后跟冒号和逗号分隔的基接口类型列表。
1 interface_base 2 : ':' interface_type_list 3 ;
对于构造的接口类型,通过对泛型类型声明采用显式基接口声明,并为基本接口声明中的每个type_parameter替换构造类型的相应type_argument,形成显式基接口。
接口的显式基接口必须至少与接口本身一样可访问(可访问性约束)。例如,在接口的interface_base中指定一个private
或internal
接口是编译时错误。public
接口直接或间接从自身继承是一个编译时错误。
接口的基接口是显式基接口及其基接口。换句话说,基本接口集是显式基接口的完整传递闭包,它们的显式基接口等等。接口继承其基接口的所有成员。在这个例子中
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 interface ITextBox: IControl 7 { 8 void SetText(string text); 9 } 10 11 interface IListBox: IControl 12 { 13 void SetItems(string[] items); 14 } 15 16 interface IComboBox: ITextBox, IListBox {}
的基本接口IComboBox
是IControl
,ITextBox
,和IListBox
。
换句话说,在IComboBox
接口之上继承的成员SetText
和SetItems
以及Paint
。
接口的每个基接口必须是输出安全的(方差安全)。实现接口的类或结构也隐式实现了所有接口的基接口。
接口体
所述interface_body接口的定义接口的成员。
1 interface_body 2 : '{' interface_member_declaration* '}' 3 ;
接口成员
接口的成员是从基接口继承的成员和接口本身声明的成员。
1 interface_member_declaration 2 : interface_method_declaration 3 | interface_property_declaration 4 | interface_event_declaration 5 | interface_indexer_declaration 6 ;
接口声明可以声明零个或多个成员。接口的成员必须是方法,属性,事件或索引器。接口不能包含常量,字段,运算符,实例构造函数,析构函数或类型,接口也不能包含任何类型的静态成员。
所有接口成员都隐式具有公共访问权限。接口成员声明包含任何修饰符是编译时错误。特别是,接口成员不能随修饰声明abstract
,public
,protected
,internal
,private
,virtual
,override
,或static
。
这个例子
1 public delegate void StringListEvent(IStringList sender); 2 3 public interface IStringList 4 { 5 void Add(string s); 6 int Count { get; } 7 event StringListEvent Changed; 8 string this[int index] { get; set; } 9 }
声明一个接口,其中包含每种可能的成员类型:方法,属性,事件和索引器。
一个interface_declaration创建一个新的声明空间(声明),以及interface_member_declaration立即被包含小号interface_declaration新成员引入此声明空间。以下规则适用于interface_member_declaration:
- 方法的名称必须与在同一接口中声明的所有属性和事件的名称不同。此外,方法的签名(签名和重载)必须与同一接口中声明的所有其他方法的签名不同,并且在同一接口中声明的两个方法可能不具有仅由
ref
和区别的签名out
。 - 属性或事件的名称必须与同一接口中声明的所有其他成员的名称不同。
- 索引器的签名必须与同一接口中声明的所有其他索引器的签名不同。
接口的继承成员特别不是接口声明空间的一部分。因此,允许接口声明与继承成员具有相同名称或签名的成员。当发生这种情况时,称派生的接口成员隐藏基接口成员。隐藏继承的成员不被视为错误,但它确实会导致编译器发出警告。要禁止警告,派生接口成员的声明必须包含一个new
修饰符,以指示派生成员是否要隐藏基本成员。在隐藏继承中进一步讨论了该主题。
如果new
修饰符包含在不隐藏继承成员的声明中,则会向该效果发出警告。通过删除new
修改器可以抑制此警告。
请注意object
,严格来说,类中的成员不是任何接口的成员(接口成员)。但是,类object
中的成员可通过任何接口类型(成员查找)中的成员查找来使用。
接口方法
接口方法使用interface_method_declaration声明:
1 interface_method_declaration 2 : attributes? 'new'? return_type identifier type_parameter_list 3 '(' formal_parameter_list? ')' type_parameter_constraints_clause* ';' 4 ;
接口方法声明的属性,return_type,identifier和formal_parameter_list与类(方法)中的方法声明具有相同的含义。不允许接口方法声明指定方法体,因此声明始终以分号结尾。
接口方法的每个形式参数类型必须是输入安全的(方差安全),并且返回类型必须是void
输出安全的或输出安全的。此外,方法的任何类型参数的每个类类型约束,接口类型约束和类型参数约束必须是输入安全的。
这些规则确保接口的任何协变或逆变使用仍然是类型安全的。例如,
interface I<out T> { void M<U>() where U : T; }
是非法的,因为T
作为类型参数约束的使用U
不是输入安全的。
如果没有这个限制,可能会以下列方式违反类型安全:
1 class B {} 2 class D : B{} 3 class E : B {} 4 class C : I<D> { public void M<U>() {...} } 5 ... 6 I<B> b = new C(); 7 b.M<E>();
这实际上是一个电话C.M<E>
。但是这个调用需要E
派生D
,所以这里会违反类型安全。
接口属性
接口属性使用interface_property_declaration声明:
1 interface_property_declaration 2 : attributes? 'new'? type identifier '{' interface_accessors '}' 3 ; 4 5 interface_accessors 6 : attributes? 'get' ';' 7 | attributes? 'set' ';' 8 | attributes? 'get' ';' attributes? 'set' ';' 9 | attributes? 'set' ';' attributes? 'get' ';' 10 ;
接口属性声明的属性,类型和标识符与类(属性)中的属性声明具有相同的含义。
接口属性声明的访问器对应于类属性声明(Accessors)的访问器,但访问器主体必须始终是分号。因此,访问器仅指示属性是读写,只读还是只写。
如果存在get访问器,则接口属性的类型必须是输出安全的,并且如果存在set访问器,则必须是输入安全的。
接口事件
接口事件使用interface_event_declaration声明:
1 interface_event_declaration 2 : attributes? 'new'? 'event' type identifier ';' 3 ;
接口索引器
接口索引器使用interface_indexer_declaration声明:
1 interface_indexer_declaration 2 : attributes? 'new'? type 'this' '[' formal_parameter_list ']' '{' interface_accessors '}' 3 ;
接口索引器声明的attributes,type和formal_parameter_list与类(索引器)中的索引器声明具有相同的含义。
接口索引器声明的访问器对应于类索引器声明(索引器)的访问器,但访问器主体必须始终是分号。因此,访问器仅指示索引器是读写,只读还是只写。
接口索引器的所有形式参数类型必须是输入安全的。此外,任何out
或ref
形式参数类型也必须是输出安全的。请注意out
,由于底层执行平台的限制,甚至参数都需要输入安全。
如果存在get访问器,则接口索引器的类型必须是输出安全的,并且如果存在set访问器,则必须是输入安全的。
接口成员访问
接口部件通过成员访问(访问成员访问)和索引器访问(索引访问)的形式的表达式I.M
和I[A]
,其中I
是一个接口类型,M
是一种方法,属性,或该接口类型的事件,并且A
是一个索引参数列表。
对于严格单继承的接口(继承链中的每个接口都具有正好零或一个直接基接口),成员查找(成员查找),方法调用(方法调用)和索引器访问(索引器访问)的效果规则与类和结构完全相同:更多派生成员隐藏具有相同名称或签名的较少派生成员。但是,对于多继承接口,当两个或多个不相关的基接口声明具有相同名称或签名的成员时,可能会出现歧义。本节介绍了此类情况的几个示例。在所有情况下,可以使用显式强制转换来解决歧义。
在这个例子中
1 interface IList 2 { 3 int Count { get; set; } 4 } 5 6 interface ICounter 7 { 8 void Count(int i); 9 } 10 11 interface IListCounter: IList, ICounter {} 12 13 class C 14 { 15 void Test(IListCounter x) { 16 x.Count(1); // Error 17 x.Count = 1; // Error 18 ((IList)x).Count = 1; // Ok, invokes IList.Count.set 19 ((ICounter)x).Count(1); // Ok, invokes ICounter.Count 20 } 21 }
前两个语句导致编译时错误,因为in 的成员查找(成员查找)是不明确的。如示例所示,通过转换为适当的基接口类型来解决歧义。这样的强制转换没有运行时成本 - 它们只是在编译时将实例视为较少派生类型。Count
IListCounter
x
在这个例子中
1 interface IInteger 2 { 3 void Add(int i); 4 } 5 6 interface IDouble 7 { 8 void Add(double d); 9 } 10 11 interface INumber: IInteger, IDouble {} 12 13 class C 14 { 15 void Test(INumber n) { 16 n.Add(1); // Invokes IInteger.Add 17 n.Add(1.0); // Only IDouble.Add is applicable 18 ((IInteger)n).Add(1); // Only IInteger.Add is a candidate 19 ((IDouble)n).Add(1); // Only IDouble.Add is a candidate 20 } 21 }
调用通过应用重载分辨率的重载决策规则来n.Add(1)
选择。类似地,调用选择。当插入显式强制转换时,只有一个候选方法,因此没有歧义。IInteger.Add
n.Add(1.0)
IDouble.Add
在这个例子中
1 interface IBase 2 { 3 void F(int i); 4 } 5 6 interface ILeft: IBase 7 { 8 new void F(int i); 9 } 10 11 interface IRight: IBase 12 { 13 void G(); 14 } 15 16 interface IDerived: ILeft, IRight {} 17 18 class A 19 { 20 void Test(IDerived d) { 21 d.F(1); // Invokes ILeft.F 22 ((IBase)d).F(1); // Invokes IBase.F 23 ((ILeft)d).F(1); // Invokes ILeft.F 24 ((IRight)d).F(1); // Invokes IBase.F 25 } 26 }
该IBase.F成员被该成员隐藏ILeft.F。d.F(1)因此ILeft.F,调用选择,即使IBase.F看起来没有隐藏在通过的访问路径中IRight。
隐藏在多继承接口中的直观规则就是:如果成员隐藏在任何访问路径中,它将隐藏在所有访问路径中。因为访问路径从IDerivedto ILeft到IBasehides IBase.F,所以成员也隐藏在从IDerived到的访问路径IRight中IBase。
完全限定的接口成员名称
接口成员有时通过其完全限定名称来引用。接口成员的完全限定名称包括声明成员的接口的名称,后跟一个点,后跟成员的名称。成员的完全限定名称引用声明成员的接口。例如,给出声明
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 interface ITextBox: IControl 7 { 8 void SetText(string text); 9 }
全名Paint
就是IControl.Paint
和的全名SetText
是ITextBox.SetText
。
在上面的示例中,无法将其Paint
称为ITextBox.Paint
。
当接口是命名空间的一部分时,接口成员的完全限定名称包括命名空间名称。例如
1 namespace System 2 { 3 public interface ICloneable 4 { 5 object Clone(); 6 } 7 }
这里,方法的完全限定名称Clone
是System.ICloneable.Clone
。
接口实现
接口可以通过类和结构来实现。为了指示类或结构直接实现接口,接口标识符包含在类或结构的基类列表中。例如:
1 interface ICloneable 2 { 3 object Clone(); 4 } 5 6 interface IComparable 7 { 8 int CompareTo(object other); 9 } 10 11 class ListEntry: ICloneable, IComparable 12 { 13 public object Clone() {...} 14 public int CompareTo(object other) {...} 15 }
直接实现接口的类或结构也直接实现所有接口的基接口。即使类或结构未明确列出基类列表中的所有基接口,也是如此。例如:
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 interface ITextBox: IControl 7 { 8 void SetText(string text); 9 } 10 11 class TextBox: ITextBox 12 { 13 public void Paint() {...} 14 public void SetText(string text) {...} 15 }
这里,类TextBox
实现了IControl
和ITextBox
。
当类C
直接实现接口时,从C派生的所有类也会隐式实现接口。类声明中指定的基接口可以构造接口类型(构造类型)。基本接口本身不能是类型参数,但它可能涉及范围内的类型参数。以下代码说明了类如何实现和扩展构造类型:
1 class C<U,V> {} 2 3 interface I1<V> {} 4 5 class D: C<string,int>, I1<string> {} 6 7 class E<T>: C<int,T>, I1<T> {}
泛型类声明的基接口必须满足已实现接口的唯一性中描述的唯一性规则。
显式接口成员实现
出于实现接口的目的,类或结构可以声明显式接口成员实现。显式接口成员实现是引用完全限定的接口成员名称的方法,属性,事件或索引器声明。例如
1 interface IList<T> 2 { 3 T[] GetElements(); 4 } 5 6 interface IDictionary<K,V> 7 { 8 V this[K key]; 9 void Add(K key, V value); 10 } 11 12 class List<T>: IList<T>, IDictionary<int,T> 13 { 14 T[] IList<T>.GetElements() {...} 15 T IDictionary<int,T>.this[int index] {...} 16 void IDictionary<int,T>.Add(int index, T value) {...} 17 }
这里IDictionary<int,T>.this
和IDictionary<int,T>.Add
是显式接口成员实现。
在某些情况下,接口成员的名称可能不适合实现类,在这种情况下,接口成员可以使用显式接口成员实现来实现。例如,实现文件抽象的类可能会实现Close
具有释放文件资源效果的成员函数,并使用显式接口成员实现实现接口的Dispose
方法IDisposable
:
1 interface IDisposable 2 { 3 void Dispose(); 4 } 5 6 class MyFile: IDisposable 7 { 8 void IDisposable.Dispose() { 9 Close(); 10 } 11 12 public void Close() { 13 // Do what's necessary to close the file 14 System.GC.SuppressFinalize(this); 15 } 16 }
在方法调用,属性访问或索引器访问中,无法通过其完全限定名称访问显式接口成员实现。显式接口成员实现只能通过接口实例访问,并且在这种情况下仅通过其成员名称引用。
这是一个显式接口成员实现,包括访问修饰符编译时错误,它是一个编译时错误,包括修饰语abstract,virtual,override,或static。
显式接口成员实现具有与其他成员不同的可访问性特征。因为显式接口成员实现永远不能通过方法调用或属性访问中的完全限定名来访问,所以它们在某种意义上是私有的。但是,由于它们可以通过接口实例访问,因此它们在某种意义上也是公开的。
显式接口成员实现有两个主要目的:
由于无法通过类或结构实例访问显式接口成员实现,因此它们允许从类或结构的公共接口中排除接口实现。当类或结构实现内部接口时,这对于该类或结构的使用者不感兴趣时,这尤其有用。
显式接口成员实现允许使用相同签名消除接口成员的歧义。如果没有显式的接口成员实现,那么类或结构将不可能具有相同签名和返回类型的接口成员的不同实现,因为类或结构不可能在所有接口成员上具有任何实现。相同的签名但具有不同的退货类型。
要使显式接口成员实现有效,类或结构必须在其基类列表中命名一个接口,该接口包含一个完全限定名称,类型和参数类型与显式接口成员实现完全匹配的成员。因此,在下面的类中
1 class Shape: ICloneable 2 { 3 object ICloneable.Clone() {...} 4 int IComparable.CompareTo(object other) {...} // invalid 5 }
IComparable.CompareTo
在编译时错误中声明结果因为IComparable
未列在基类列表中Shape
而且不是基本接口ICloneable
。同样,在声明中
1 class Shape: ICloneable 2 { 3 object ICloneable.Clone() {...} 4 } 5 6 class Ellipse: Shape 7 { 8 object ICloneable.Clone() {...} // invalid 9 }
ICloneable.Clone
in 的声明Ellipse
导致编译时错误,因为ICloneable
未在基类列表中明确列出Ellipse
。
接口成员的标准名称必须引用声明该成员的接口。因此,在声明中
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 interface ITextBox: IControl 7 { 8 void SetText(string text); 9 } 10 11 class TextBox: ITextBox 12 { 13 void IControl.Paint() {...} 14 void ITextBox.SetText(string text) {...} 15 }
显式接口成员的实现Paint
必须写成IControl.Paint
。
已实现接口的唯一性
通用类型声明实现的接口必须对所有可能的构造类型保持唯一。如果没有这个规则,就不可能确定调用某些构造类型的正确方法。例如,假设允许使用泛型类声明,如下所示:
1 interface I<T> 2 { 3 void F(); 4 } 5 6 class X<U,V>: I<U>, I<V> // Error: I<U> and I<V> conflict 7 { 8 void I<U>.F() {...} 9 void I<V>.F() {...} 10 }
如果允许这样做,则无法确定在以下情况下要执行的代码:
1 I<int> x = new X<int,int>(); 2 x.F();
要确定泛型类型声明的接口列表是否有效,请执行以下步骤:
- 我们
L
是在一个泛型类,结构或接口声明直接指定的接口列表C
。 - 添加到
L
已有接口的任何基接口L
。 - 从中删除任何重复项
L
。 - 如果在
C
将类型参数替换为from 之后创建的任何可能的构造类型L
导致两个接口L
相同,则声明C
无效。在确定所有可能的构造类型时,不考虑约束声明。
在X
上面的类声明中,接口列表L
由I<U>
和组成I<V>
。声明无效,因为任何具有U
和V
相同类型的构造类型都会导致这两个接口是相同的类型。
在不同的继承级别指定的接口可以统一:
1 interface I<T> 2 { 3 void F(); 4 } 5 6 class Base<U>: I<U> 7 { 8 void I<U>.F() {...} 9 } 10 11 class Derived<U,V>: Base<U>, I<V> // Ok 12 { 13 void I<V>.F() {...} 14 }
此代码是有效的,即使Derived<U,V>
同时实现了I<U>
和I<V>
。代码
1 I<int> x = new Derived<int,int>(); 2 x.F();
泛型方法的实现
当泛型方法隐式实现接口方法时,为每个方法类型参数指定的约束在两个声明中必须是等效的(在用适当的类型参数替换任何接口类型参数之后),其中方法类型参数由顺序位置标识,左对。
但是,当泛型方法显式实现接口方法时,实现方法不允许任何约束。相反,约束是从接口方法继承的
1 interface I<A,B,C> 2 { 3 void F<T>(T t) where T: A; 4 void G<T>(T t) where T: B; 5 void H<T>(T t) where T: C; 6 } 7 8 class C: I<object,C,string> 9 { 10 public void F<T>(T t) {...} // Ok 11 public void G<T>(T t) where T: C {...} // Ok 12 public void H<T>(T t) where T: string {...} // Error 13 }
该方法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>
。此接口方法只能使用显式接口成员实现来实现:
1 class C: I<object,C,string> 2 { 3 ... 4 5 public void H<U>(U u) where U: class {...} 6 7 void I<object,C,string>.H<T>(T t) { 8 string s = t; // Ok 9 H<T>(t); 10 } 11 }
接口映射
类或结构必须提供类或结构的基类列表中列出的接口的所有成员的实现。在实现类或结构中定位接口成员的实现的过程称为接口映射。
类或结构的接口映射C
定位了基类列表中指定的每个接口的每个成员的实现C
。特定接口成员的实现I.M
,其中I
是M
声明成员的接口,通过检查每个类或结构来确定S
,从C
每个连续的基类开始并重复C
,直到找到匹配:
- 如果
S
包含匹配的显式接口成员实现的声明I
和M
,那么这个成员就是执行I.M
。 - 否则,如果
S
包含一个匹配的非静态公共成员的声明M
,那么这个成员就是执行I.M
。如果多个成员匹配,则未指定哪个成员是实现I.M
。这种情况只有S
在构造类型中才会发生,其中泛型类型中声明的两个成员具有不同的签名,但类型参数使它们的签名相同。
如果无法为基类列表中指定的所有接口的所有成员定位实现,则会发生编译时错误C
。请注意,接口的成员包括从基接口继承的成员。
出于接口映射的目的,类成员在以下情况下A
匹配接口成员B
:
A
和B
是方法,以及名称,类型,和正式参数列表A
和B
是相同的。A
并且B
是属性,名称和类型,A
并且B
是相同的,并且A
具有相同的访问器B
(A
如果它不是显式的接口成员实现,则允许具有其他访问器)。A
和B
是事件,以及名称和类型A
和B
是相同的。A
并且B
是索引器,类型和形式参数列表A
和B
相同,并且A
具有相同的访问器B
(A
如果它不是显式接口成员实现,则允许具有附加访问器)。
接口映射算法的显着含义是:
- 在确定实现接口成员的类或结构成员时,显式接口成员实现优先于同一类或结构中的其他成员。
- 非公共成员和静态成员都不参与接口映射。
在这个例子中
1 interface ICloneable 2 { 3 object Clone(); 4 } 5 6 class C: ICloneable 7 { 8 object ICloneable.Clone() {...} 9 public object Clone() {...} 10 }
该ICloneable.Clone
成员C
成为执行Clone
中ICloneable
,因为显式接口成员实现优先于其他成员。
如果类或结构实现了两个或多个包含具有相同名称,类型和参数类型的成员的接口,则可以将每个接口成员映射到单个类或结构成员上。例如
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 interface IForm 7 { 8 void Paint(); 9 } 10 11 class Page: IControl, IForm 12 { 13 public void Paint() {...} 14 }
在此,Paint
两者的方法IControl
和IForm
被映射到Paint
在方法Page
。当然也可以为这两种方法分别使用显式接口成员实现。
如果类或结构实现了包含隐藏成员的接口,则必须通过显式接口成员实现来实现某些成员。例如
1 interface IBase 2 { 3 int P { get; } 4 } 5 6 interface IDerived: IBase 7 { 8 new int P(); 9 }
此接口的实现将需要至少一个显式接口成员实现,并将采用以下形式之一
1 class C: IDerived 2 { 3 int IBase.P { get {...} } 4 int IDerived.P() {...} 5 } 6 7 class C: IDerived 8 { 9 public int P { get {...} } 10 int IDerived.P() {...} 11 } 12 13 class C: IDerived 14 { 15 int IBase.P { get {...} } 16 public int P() {...} 17 }
当一个类实现具有相同基接口的多个接口时,基本接口只能有一个实现。在这个例子中
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 interface ITextBox: IControl 7 { 8 void SetText(string text); 9 } 10 11 interface IListBox: IControl 12 { 13 void SetItems(string[] items); 14 } 15 16 class ComboBox: IControl, ITextBox, IListBox 17 { 18 void IControl.Paint() {...} 19 void ITextBox.SetText(string text) {...} 20 void IListBox.SetItems(string[] items) {...} 21 }
它是不可能有独立的实现IControl
在基类列表得名,IControl
通过继承ITextBox
,以及IControl
由继承IListBox
。实际上,这些接口没有单独标识的概念。相反,的实现ITextBox
和IListBox
共享相同的实现IControl
,并且ComboBox
被简单地认为是实现了三个接口IControl
,ITextBox
和IListBox
。
基类的成员参与接口映射。在这个例子中
1 interface Interface1 2 { 3 void F(); 4 } 5 6 class Class1 7 { 8 public void F() {} 9 public void G() {} 10 } 11 12 class Class2: Class1, Interface1 13 { 14 new public void G() {} 15 }
该方法F
中Class1
在使用Class2
的实现的Interface1
。
接口实现继承
类继承其基类提供的所有接口实现。
在没有显式重新实现接口的情况下,派生类不能以任何方式改变它从其基类继承的接口映射。例如,在声明中
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 class Control: IControl 7 { 8 public void Paint() {...} 9 } 10 11 class TextBox: Control 12 { 13 new public void Paint() {...} 14 }
的Paint
在方法TextBox
隐藏了Paint
在方法Control
,但它并没有改变的映射Control.Paint
到IControl.Paint
,并调用到Paint
通过类实例和接口实例将具有以下效果
1 Control c = new Control(); 2 TextBox t = new TextBox(); 3 IControl ic = c; 4 IControl it = t; 5 c.Paint(); // invokes Control.Paint(); 6 t.Paint(); // invokes TextBox.Paint(); 7 ic.Paint(); // invokes Control.Paint(); 8 it.Paint(); // invokes Control.Paint();
但是,当接口方法映射到类中的虚方法时,派生类可能会覆盖虚方法并更改接口的实现。例如,将上面的声明重写为
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 class Control: IControl 7 { 8 public virtual void Paint() {...} 9 } 10 11 class TextBox: Control 12 { 13 public override void Paint() {...} 14 }
现在将观察到以下效果
1 Control c = new Control(); 2 TextBox t = new TextBox(); 3 IControl ic = c; 4 IControl it = t; 5 c.Paint(); // invokes Control.Paint(); 6 t.Paint(); // invokes TextBox.Paint(); 7 ic.Paint(); // invokes Control.Paint(); 8 it.Paint(); // invokes TextBox.Paint();
由于显式接口成员实现不能声明为虚拟,因此无法覆盖显式接口成员实现。但是,显式接口成员实现调用另一个方法是完全有效的,并且可以将其他方法声明为virtual以允许派生类覆盖它。例如
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 class Control: IControl 7 { 8 void IControl.Paint() { PaintControl(); } 9 protected virtual void PaintControl() {...} 10 } 11 12 class TextBox: Control 13 { 14 protected override void PaintControl() {...} 15 }
这里,派生自的类Control
可以IControl.Paint
通过重写PaintControl
方法来专门化实现。
重新实现接口
允许继承接口实现的类通过将其包含在基类列表中来重新实现接口。
接口的重新实现遵循与接口的初始实现完全相同的接口映射规则。因此,继承的接口映射对于为接口的重新实现而建立的接口映射没有任何影响。例如,在声明中
1 interface IControl 2 { 3 void Paint(); 4 } 5 6 class Control: IControl 7 { 8 void IControl.Paint() {...} 9 } 10 11 class MyControl: Control, IControl 12 { 13 public void Paint() {} 14 }
这一事实Control
映射IControl.Paint
到Control.IControl.Paint
不影响在重新实现MyControl
,它映射IControl.Paint
到MyControl.Paint
。
继承的公共成员声明和继承的显式接口成员声明参与重新实现的接口的接口映射过程。例如
1 interface IMethods 2 { 3 void F(); 4 void G(); 5 void H(); 6 void I(); 7 } 8 9 class Base: IMethods 10 { 11 void IMethods.F() {} 12 void IMethods.G() {} 13 public void H() {} 14 public void I() {} 15 } 16 17 class Derived: Base, IMethods 18 { 19 public void F() {} 20 void IMethods.H() {} 21 }
在这里,实施IMethods
在Derived
接口方法映射到Derived.F
,Base.IMethods.G
,Derived.IMethods.H
,和Base.I
。
当一个类实现一个接口时,它隐式地也实现了该接口的所有基接口。同样,接口的重新实现也隐含地是所有接口的基接口的重新实现。例如
1 interface IBase 2 { 3 void F(); 4 } 5 6 interface IDerived: IBase 7 { 8 void G(); 9 } 10 11 class C: IDerived 12 { 13 void IBase.F() {...} 14 void IDerived.G() {...} 15 } 16 17 class D: C, IDerived 18 { 19 public void F() {...} 20 public void G() {...} 21 }
在这里,重新实现IDerived
也重新实现IBase
,映射IBase.F
到D.F
。
抽象类和接口
与非抽象类一样,抽象类必须提供类的基类列表中列出的接口的所有成员的实现。但是,允许抽象类将接口方法映射到抽象方法。例如
1 interface IMethods 2 { 3 void F(); 4 void G(); 5 } 6 7 abstract class C: IMethods 8 { 9 public abstract void F(); 10 public abstract void G(); 11 }
这里,IMethods
映射F
和G
抽象方法的实现,必须在派生自的非抽象类中重写C
。
请注意,显式接口成员实现不能是抽象的,但显式接口成员实现当然可以调用抽象方法。例如
1 interface IMethods 2 { 3 void F(); 4 void G(); 5 } 6 7 abstract class C: IMethods 8 { 9 void IMethods.F() { FF(); } 10 void IMethods.G() { GG(); } 11 protected abstract void FF(); 12 protected abstract void GG(); 13 }
在这里,从派生的非抽象类C
将需要重写FF
和GG
,从而提供实际执行IMethods
。