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),后跟一个可选的部分修饰符(§15.2.7),后跟一个关键字interface
和一个用于命名接口的标识符,后跟可选的variant_type_parameter_list规范(§18.2.3),后跟可选的interface_base specification (§18.2.4),后跟可选的type_parameter_constraints_clause规范(§15.2.5),后跟interface_body(§18.3),可选后跟分号。
接口声明不应提供 type_parameter_constraints_clause,除非它还提供 variant_type_parameter_list。
提供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)包括辅助功能规范(通过 protected
public
internal
15.2.2 和private
修饰符)时,§15.2.2 中的规则适用。
18.2.3 Variant 类型参数列表
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
,则类型参数据说是 逆变的。 如果没有方差注释,则类型参数据说是 固定的。
示例:在以下各项中:
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 类型为输出不安全:
T
是逆变类型参数T
是具有输出不安全元素类型的数组类型T
是从泛型类型构造的接口或委托类型Sᵢ,... Aₑ
S<Xᵢ, ... Xₑ>
,其中至少Aᵢ
保留以下一种类型:Xᵢ
是协变或固定的,并且Aᵢ
输出不安全。Xᵢ
是逆变量或固定的,并且Aᵢ
是输入不安全的。
如果以下类型之一保留,则 T 类型为输入不安全:
T
是协变类型参数T
是具有输入不安全元素类型的数组类型T
是从泛型类型构造的接口或委托类型S<Aᵢ,... Aₑ>
S<Xᵢ, ... Xₑ>
,其中至少Aᵢ
保留以下一种类型:Xᵢ
是协变或固定的,并且Aᵢ
是输入不安全的。Xᵢ
是逆变量或固定,并且Aᵢ
输出不安全。
直观地说,输出不安全类型在输出位置中被禁止,并且输入不安全的类型在输入位置中被禁止。
如果类型不是输出不安全,则为输出安全类型;如果类型不是输入不安全类型,则为输入安全。
18.2.3.3 方差转换
方差批注的目的是为接口和委托类型提供更宽松(但仍类型安全)的转换。 为此,隐式转换(§10.2)和显式转换(§10.3)的定义使用方差-可转换的概念,如下所述:
如果T
类型T<Aᵢ, ..., Aᵥ>
是使用变体类型参数声明的接口或委托类型,并且对于以下每种变体类型参数Xᵢ
T<Xᵢ, ..., Xᵥ>
之一,则类型可转换为T<Bᵢ, ..., Bᵥ>
类型:
Xᵢ
是协变的,并且存在从 到Aᵢ
的隐式引用或标识转换Bᵢ
Xᵢ
是逆变量,并且存在Bᵢ
隐式引用或标识转换Aᵢ
Xᵢ
是固定的,标识转换存在自Aᵢ
Bᵢ
18.2.4 基本接口
接口可以从零个或多个接口类型继承,这些类型称为 接口的显式基接口 。 当接口具有一个或多个显式基接口时,在该接口的声明中,接口标识符后跟冒号和基接口类型的逗号分隔列表。
interface_base
: ':' interface_type_list
;
显式基接口可以构造接口类型 (§8.4, §18.2)。 基接口本身不能是类型参数,尽管它可能涉及范围中的类型参数。
对于构造接口类型,显式基接口是通过对泛型类型声明采用显式基接口声明和替换基接口声明中每个 type_parameter (构造类型的对应 type_argument )来构成的。
接口的显式基接口应至少与接口本身(§7.5.5.5)一样可访问。
注意:例如,在接口的interface_base
public
中指定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); }
在类型参数
T
被替换为string[,]
后,接口IDerived
将继承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
;
接口声明声明零个或多个成员。 接口的成员应是方法、属性、事件或索引器。 接口不能包含常量、字段、运算符、实例构造函数、终结器或类型,也不能包含任何类型的静态成员。
所有接口成员都隐式具有公共访问权限。 这是接口成员声明包含任何修饰符的编译时错误。
interface_declaration创建新的声明空间(§7.3),interface_declaration立即包含的类型参数和interface_member_declaration将新成员引入此声明空间。 以下规则适用于 interface_member_declarations:
- 接口声明variant_type_parameter_list中类型参数的名称应与同一variant_type_parameter_list中所有其他类型参数的名称不同,并且与接口的所有成员的名称不同。
- 方法的名称应不同于在同一接口中声明的所有属性和事件的名称。 此外,方法的签名(§7.6)应与同一接口中声明的所有其他方法的签名不同,在同一接口中声明的两个方法不得具有唯
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_declarations 声明:
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_type、ref_return_type、标识符和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_declarations 声明的:
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 访问器,接口属性的类型应是输出安全的,如果存在 set 访问器,则为输入安全。
18.4.4 接口事件
接口事件是使用 interface_event_declarations 声明的:
interface_event_declaration
: attributes? 'new'? 'event' type identifier ';'
;
接口事件声明的属性、类型和标识符与类(§15.8)中的事件声明的属性、类型和标识符的含义相同。
接口事件的类型应是输入安全的。
18.4.5 接口索引器
接口索引器使用 interface_indexer_declarations 声明:
interface_indexer_declaration
: attributes? 'new'? type 'this' '[' parameter_list ']'
'{' interface_accessors '}'
| attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
'{' ref_interface_accessor '}'
;
接口索引器声明的属性、类型和parameter_list与类中索引器声明的属性、类型和parameter_list的含义相同。
接口索引器声明的访问器对应于类索引器声明(§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
是索引器参数列表。
对于严格单一继承的接口(继承链中的每个接口完全具有零个或一个直接基接口),成员查找(§12.5)、方法调用的影响(§12.8.9)。 2)和索引器访问(§12.8.11.3)规则与类和结构完全相同:更多派生成员隐藏具有相同名称或签名的派生成员。 但是,对于多继承接口,当两个或多个不相关的基接口声明具有相同名称或签名的成员时,可能会出现歧义。 此子引用演示了几个示例,其中一些示例会导致歧义和其他不明确之处。 在所有情况下,都可以使用显式强制转换来解决歧义。
示例:在以下代码中
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 } }
前两个语句会导致编译时错误,因为其中
IListCounter
的成员查找(§12.5)Count
不明确。 如示例所示,通过强制转换为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 } }
通过应用 §12.6.4 的重载解析规则来选择
IInteger.Add
调用n.Add(1)
。 同样,调用n.Add(1.0)
选择IDouble.Add
。 插入显式强制转换时,只有一个候选方法,因此没有歧义。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
的访问路径中。隐藏在多继承接口中的直观规则就是这样:如果成员隐藏在任何访问路径中,则它隐藏在所有访问路径中。 由于访问路径从到
ILeft
隐藏,因此成员也隐藏在访问路径中。IDerived
IBase
IRight
IBase.F
IBase
IDerived
end 示例
18.5 限定接口成员名称
接口成员有时由其 限定的接口成员名称引用。 接口成员的限定名称包含声明成员的接口的名称,后跟一个点,后跟成员的名称。 成员的限定名称引用声明成员的接口。
示例:给定声明
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); }
限定名称
Paint
为 isIControl.Paint
且 SetText 的限定名称为ITextBox.SetText
。 在上面的示例中,无法引用Paint
为ITextBox.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
具有释放文件资源效果的成员函数,并使用显式接口成员实现实现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); } }
end 示例
在方法调用、属性访问、事件访问或索引器访问中,无法通过其限定的接口成员名称访问显式接口成员实现。 显式接口成员实现只能通过接口实例进行访问,在这种情况下,只需由其成员名称引用。
对于显式接口成员实现,包括除或async
以外的extern
任何修饰符(§15.6)是编译时错误。
显式接口方法实现 包含 type_parameter_constraints_clause是编译时错误。 泛型显式接口方法实现的约束继承自接口方法。
注意:显式接口成员实现具有不同于其他成员的辅助功能特征。 由于显式接口成员实现从来不能通过方法调用或属性访问中的限定接口成员名称进行访问,因此它们从某种意义上说是私有的。 但是,由于可以通过接口访问它们,因此它们在某种意义上也与声明它们的接口一样公开。 显式接口成员实现提供两个主要用途:
- 由于无法通过类或结构实例访问显式接口成员实现,因此允许从类或结构的公共接口中排除接口实现。 当类或结构实现对该类或结构的使用者不感兴趣的内部接口时,这特别有用。
- 显式接口成员实现允许消除具有相同签名的接口成员的歧义。 如果没有显式接口成员实现,类或结构不可能具有具有相同签名和返回类型的接口成员的不同实现,类或结构不可能拥有任何具有相同签名但具有不同返回类型的接口成员的任何实现。
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 }
编译时错误的声明
ICloneable.Clone
Ellipse
,因为ICloneable
基类列表中Ellipse
未显式列出。end 示例
显式接口成员实现的限定接口成员名称应引用声明成员的接口。
示例:因此,在声明中
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} }
画图的显式接口成员实现必须编写为
IControl.Paint
,而不是ITextBox.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 示例
若要确定泛型类型声明的接口列表是否有效,请执行以下步骤:
- 让我们
L
直接列出在泛型类、结构或接口声明中C
指定的接口。 - 添加到已加入
L
L
的接口的任何基接口。 - 从
L
中删除任何重复项。 - 如果从
C
中创建的任何可能的构造类型,在类型参数被替换成L
后,会导致两个接口L
相同,则声明C
无效。 确定所有可能的构造类型时,不考虑约束声明。
注意:在上面的类声明中
X
,接口列表L
由l<U>
和I<V>
. 声明无效,因为具有同V
一类型的任何构造类型U
都会导致这两个接口是相同的类型。 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();
调用方法 Derived
,因为 Derived<int,int>'
有效地重新实现 I<int>
(§18.6.7)。
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 的赋值是有效的,因为
T
继承了一个约束T: string
,即使此约束在源代码中无法表达。 end 示例
18.6.5 接口映射
类或结构应提供类或结构基类列表中所列接口的所有成员的实现。 在实现类或结构中查找接口成员实现的过程称为接口映射。
类或结构的 C
接口映射为基类列表中 C
指定的每个接口的每个成员找到实现。 特定接口成员 I.M
的实现(其中 I
声明成员 M
的接口)通过检查每个类或结构 S
来确定,从 C
每个连续基类开始并重复每个基类 C
,直到匹配找到:
- 如果
S
包含与匹配I
M
的显式接口成员实现的声明,则此成员是实现的I.M
。 - 否则,如果
S
包含与匹配M
的非静态公共成员的声明,则此成员是实现的I.M
。 如果多个成员匹配,则未指定哪个成员是实现的成员I.M
。 仅当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 示例
如果类或结构实现两个或更多个接口,其中包含具有相同名称、类型和参数类型的成员,则可以将每个接口成员映射到单个类或结构成员。
示例:
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }
此处,
Paint
这两IControl
种方法的方法IForm
都Paint
映射到方法中Page
。 当然,也可以为这两种方法使用单独的显式接口成员实现。end 示例
如果类或结构实现包含隐藏成员的接口,则某些成员可能需要通过显式接口成员实现来实现。
示例:
interface IBase { int P { get; } } interface IDerived : IBase { new int P(); }
此接口的实现至少需要一个显式接口成员实现,并且采用以下形式之一
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 示例
当类实现具有相同基接口的多个接口时,基接口只能有一个实现。
示例:在以下代码中
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
以及继承者IListBox
具有IControl
单独的IControl
实现。 事实上,这些接口没有单独的标识的概念。 相反,实现和共享相同的实现ITextBox
IControl
,并且ComboBox
只是考虑实现三个接口,IControl
ITextBox
以及IListBox
。IListBox
end 示例
基类的成员参与接口映射。
示例:在以下代码中
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }
中
Class1
的方法F
用于Class2's
实现Interface1
。end 示例
18.6.6 接口实现继承
类继承其基类提供的所有接口实现。
如果不显式重新实现接口,派生类无法以任何方式更改从基类继承的接口映射。
示例:在声明中
interface IControl { void Paint(); } class Control : IControl { public void Paint() {...} } class TextBox : Control { public new void Paint() {...} }
方法
Paint
隐藏TextBox
Paint
方法,Control
但它不会更改到的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
IControl.Paint
这一事实Control
不会影响重新实现,而重新实现MyControl
则映射到IControl.Paint
MyControl.Paint
该实现。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() {} }
在这里,接口方法的实现
IMethods
将接口方法映射到Derived.F
、Base.IMethods.G
和Derived.IMethods.H
Base.I
。Derived
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.F
D.F
。IBase
end 示例
18.6.8 抽象类和接口
与非抽象类一样,抽象类应提供类基类列表中所列接口的所有成员的实现。 但是,允许抽象类将接口方法映射到抽象方法。
示例:
interface IMethods { void F(); void G(); } abstract class C : IMethods { public abstract void F(); public abstract void G(); }
在这里,映射
F
和G
抽象方法的IMethods
实现,这些方法应在派生自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 示例