c#中的接口

 一个接口定义了一个协议。一个实现了一个接口的类或结构必须符合它的协议。一个接口可以从多个基本接口继承,而一个类或结构也可以实现多个接口。
接口可以包含方法、属性、事件和索引。接口自己不为它所定义的成员提供执行程序。接口只是指定必须被实现这个接口的类或接口提供的成员。
13.1 接口声明
一个接口声明是一个类型声明 (§9.5) 它声明了新的接口类型。
interface-declaration:
attributesopt   interface-modifiersopt   interface   identifier   interface-baseopt   interface-body   ;opt
一个接口声明由下面的方式组成:一个可选的属性集合 (§错误!未找到引用源。), 跟着一个可选的接口修饰符集合 (§13.1.1), 跟着关键词interface 和一个命名接口的标识符,还可以跟着一个可选的接口基本说明(§13.1.2),跟着一个接口主体 (§13.1.3), 最后可以选择跟一个分号。
13.1.1 接口修饰符
一个接口声明可以包括一个接口修饰符序列:
interface-modifiers:
interface-modifier
interface-modifiers   interface-modifier
interface-modifier:
new
public
protected
internal
private
对于相同的修饰符在一个接口声明中出现多次是错误的。
new 修饰符是在嵌套接口中唯一被允许存在的修饰符。它说明用相同的名称隐藏一个继承的成员,就像在§10.2.2中所描述的一样。
public, protected, internal和private 修饰符控制接口的访问能力。根据发生接口声明的上下文,只有这些修饰符中的一些会被允许(§错误!未找到引用源。)。
13.1.2 基本接口
一个接口可以从零或多个接口继承,那些被称为这个接口的显式基本接口。当一个接口有比零多的显式基本接口时,那么在接口的声明中的形式为,接口标识符后面跟着由一个冒号和一个用逗号分开的基本接口标识符列表。
interface-base:
:   interface-type-list
一个接口的显式基本接口必须至少同接口本身一样可访问 (§错误!未找到引用源。)。例如,在一个公共接口的基本接口中指定一个私有或内部的接口是错误的。
一个接口直接或间接地从它自己继承是错误的。
接口的基本接口都是显式基本接口,并且是它们的基本接口。换句话说,基本接口的集合完全由显式基本接口和它们的显式基本接口等等组成。在下面的例子中
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 就像Paint一样继承成员SetText 和 SetItems。
一个实现了接口的类或结构也隐含地实现了所有接口的基本接口。
13.1.3 接口主体
一个接口的接口主体定义了接口的成员。
interface-body:
{   interface-member-declarationsopt   }
13.2 接口成员
一个接口的成员都是从基本接口继承的成员和被接口自己声明的成员。
interface-member-declarations:
interface-member-declaration
interface-member-declarations   interface-member-declaration
interface-member-declaration:
interface-method-declaration
interface-property-declaration
interface-event-declaration
interface-indexer-declaration
一个接口声明会声明零或多个成员。一个接口的成员必须是方法、属性、事件或索引。一个接口不能包含常数、域、操作符、构造函数、静态构造函数或类型,也不能包括任何类型的静态成员。
所有接口成员隐含的都有公共访问。接口成员声明中包含任何修饰符是错误的。特别是,接口成员不能被用abstract, public, protected, internal, private, virtual, override, 或 static 修饰符声明。
例子
public delegate void StringListEvent(IStringList sender);
public interface IStringList
{
void Add(string s);
int Count { get; }
event StringListEvent Changed;
string this[int index] { get; set; }
}
声明了一个包含每个可能类型成员的接口:一个方法,一个属性,一个事件和一个索引。
一个接口声明创建了一个新的声明域 (§3.1), 而接口成员声明直接被把新成员引入这个声明域的接口声明所包含。下面的规则适用于接口成员声明:
· 方法的名称必须与在同一接口中声明的所有属性和事件的名称不同。另外,方法的签名 (§3.4) 必须与在同一接口中声明的其他方法的签名不同。
· 属性或事件的名称必须与在相同接口中声明所有其他成员的名称不同。
· 一个索引得签名必须与相同接口中声明的所有其他索引的签名不同。
一个接口的继承成员不是这个接口的声明域的一部分。因此,一个接口允许用与继承成员相同的名称或签名声明一个成员。当这个发生时,派生接口成员被称为隐藏了基本接口成员。隐藏一个继承成员不是错误的,但是它会造成编译器给出一个警告。为了禁止这个警告,对于派生接口成员的声明必须包含一个new修饰符来指示派生的成员要隐藏基本成员。这个题目将在§3.5.1.2中讨论。
如果一个new修饰符被包含在一个没有隐藏继承成员的声明中,就会对这种情况给出一个警告。可以通过去掉new修饰符来禁止这个警告。
13.2.1 接口方法
接口方法使用接口方法声明(interface-method-declaration)来声明:
interface-method-declaration:
attributesopt   newopt   return-type   identifier   (   formal-parameter-listopt   )   ;
接口方法声明中的属性(attributes), 返回类型(return-type), 标识符(identifier), 和 形式参数列表(formal-parameter-lis)t 与一个类(§错误!未找到引用源。) 的方法声明中的那些有相同的意义。一个接口方法声明不允许指定一个方法主体,而声明通常用一个分号结束。
13.2.2 接口属性
接口属性用接口属性声明(interface-property-declarations)来声明:
interface-property-declaration:
attributesopt   newopt   type   identifier   {   interface-accessors   }
interface-accessors:
get   ;
set   ;
get   ;   set   ;
set   ;   get   ;
接口属性声明的属性(attributes), 类型(type), 和 标识符(identifier) 与在一个类中的属性声明有相同的意义(§10.6)。
接口属性声明的访问符与类属性声明的访问符(§10.6.5)相对应,除了访问符主体通常必须用分号。因此,无论属性是读写、只读或只写,访问符都完全确定。
13.2.3 接口事件
接口事件用接口事件声明(interface-event-declarations)来声明:
interface-event-declaration:
attributesopt   newopt   event   type   identifier   ;
接口事件声明的属性(attributes), 类型(type), 和 标识符(identifier) 与类的事件声明(§10.7)中的那些有相同的意义。
13.2.4 接口索引
接口索引使用接口索引声明(interface-indexer-declarations)来声明:
interface-indexer-declaration:
attributesopt   newopt   type   this   [   formal-index-parameter-list   ]   {   interface-accessors   }
接口索引声明中的属性(attributes), 类型(type), 和形式参数列表 (formal-parameter-list) 与类的索引声明的那些有相同的意义(§10.8)。
接口索引声明的访问符与类索引声明的访问符(§10.6.5)相对应,除了访问符主体通常必须用分号。因此,无论索引是读写、只读或只写,访问符都完全确定。
13.2.5 接口成员访问
可以通过形式为I.M 和 I[A]的成员访问(§7.5.4)和索引访问(§7.5.6.2)表达式来访问接口成员,这里I是接口类型的实例,M是那个接口类型的一个方法、属性和事件,而A是一个索引参数列表。
对于严格的单继承的接口(每个继承链中的接口没有或有一个直接基本接口),成员查找(§7.3)、方法调用(§7.5.5.1)和索引访问(§7.5.6.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, Count is ambiguous
x.Count = 1; // Error, Count is ambiguous
((IList)x).Count = 1; // Ok, invokes IList.Count.set
((ICounter)x).Count(1); // Ok, invokes ICounter.Count
}
}
前两个语句造成编译时的错误,因为Count in IListCounter 的成员查找(§7.3)造成了不确定。就像例子中所演示的一样,通过把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); // Error, both Add methods are applicable
n.Add(1.0); // Ok, only IDouble.Add is applicable
((IInteger)n).Add(1); // Ok, only IInteger.Add is a candidate
((IDouble)n).Add(1); // Ok, only IDouble.Add is a candidate
}
}
调用n.Add(1) 是不明确的,因为一个方法调用 (§7.5.5.1) 被声明为相同的类型。但是,调用n.Add(1.0) 被允许,因为只有IDouble.Add 是可用的。当加入了显式的嵌入,这里就只有一个候选的方法,这样就不会有不确定存在。
在例子中
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 的访问路径中也被隐藏。
13.3 完全有效的接口成员名称
一个接口成员有时被它的完全有效名称(fully qualified name)所调用。一个接口成员的完全有效名称由下面的形式组成:声明这个变量的接口的名称后面跟着一个点,再跟着这个成员的名称。例如,给出声明
interface IControl
{
void Paint();
}
interface ITextBox: IControl
{
void SetText(string text);
}
Paint 的完全有效名称是IControl.Paint ,而SetText 的完全有效名称是 ITextBox.SetText。
注意一个成员的完全有效名称在声明这个成员的接口中引用这个接口。因此,在上面的例子中,不可能用ITextBox.Paint 引用Paint。
当一个接口是一个名称空间的一部分时,这个接口成员的完全有效名称包括名称空间名称。例如
namespace System
{
public interface ICloneable
{
object Clone();
}
}
这里,方法Clone的完全有效名称是System.ICloneable.Clone。
13.4 接口实现
接口可以北类和结构实现。为了指出一个类或结构实现一个接口,接口标识符被包含在类或结构的基本类列表中。
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) {...}
}
这里,类TextBox 实现了IControl 和 ItextBox。
13.4.1 显式接口成员实现程序
出于实现接口的目的,一个类或结构可以声明显式接口成员实现程序(explicit interface member implementations )。一个显式接口成员实现程序是一个方法、属性、事件或索引声明,它引用一个完全有效接口成员名称。例如
interface ICloneable
{
object Clone();
}
interface IComparable
{
int CompareTo(object other);
}
class ListEntry: ICloneable, IComparable
{
object ICloneable.Clone() {...}
int IComparable.CompareTo(object other) {...}
}
这里, ICloneable.Clone 和 IComparable.CompareTo 都是显式接口成员实现程序。
在一个方法调用、属性访问或索引访问中,可以通过完全有效名称访问显式接口成员实现程序。一个显式接口成员实现程序可以只通过接口实例访问,而在那种情况下,引用完全通过成员名称实现。
一个显式接口成员实现程序中包含访问修饰符是错误的,因为它不能包含abstract, virtual, override, 或 static 修饰符。
显式接口成员实现程序跟其他成员相比,有不同的访问能力特性。因为显式接口成员实现程序通过方法调用和属性访问的完全有效名称是不可访问的,所以,它们从感觉上是私有的。但是,由于它们可以通过一个接口实例访问,从感觉上它们又是公共的。
显式接口成员实现程序主要服务于两个目的:
· 因为显式接口成员实现程序通过类或结构实例是不可访问的,所以允许接口实现从类或结构的公共接口中执行。当一个类或结构实现一个对类或结构的成员用户不感兴趣的内部接口时,这就很有用了。
· 显式接口成员实现程序允许用相同的签名消除接口成员的歧义。没有显式接口成员实现程序,一个类或结构包含有签名和返回类型都相同的接口成员的不同实现是不可能的,而一个类或结构包含有相同的签名和不同的返回类型的所有接口成员的实现是可能的。
为了使显式接口成员实现程序有效,类或成员必须在它的基本类列表中命名一个接口,这个列表中包含一个成员,它的全部有效名称、类型和参数类型与显式接口成员实现程序的那些要完全一致。因此,在下面的类中
class Shape: ICloneable
{
object ICloneable.Clone() {...}
int IComparable.CompareTo(object other) {...}
}
IComparable.CompareTo 的声明是无效的,因为IComparable 没有被列在Shape的基本类列表中,并且不是ICloneable 的一个基本接口。与此类似,在声明中
class Shape: ICloneable
{
object ICloneable.Clone() {...}
}
class Ellipse: Shape
{
object ICloneable.Clone() {...}
}
Ellipse中的声明ICloneable.Clone 是错误的,因为ICloneable 没有在Ellipse的基本类列表中明显列出。
一个接口成员的完全有效名称必须在声明这个成员的接口中引用这个接口。因此,在这个声明中
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。
13.4.2 接口映射
一个类或结构必须提供在类或结构的基本类列表中列出的接口中所有成员的实现。在一个类或结构中实现对接口成员的定位的过程被称为接口映射。
一个类或结构C的接口映射为位于C的基本类列表中指定的每个接口的每个成员确定实现程序。一个特殊接口成员I.M 的实现通过对每个类或类型S进行检查来确定,这个检查从C开始,并且对C的每个继承的基本类重复进行,直到发现匹配,这里I是一个接口,而在I中声明了成员M:
· 如果S中包含一个与I和M匹配的显式接口成员实现程序的声明,那么这个成员就是I.M 的实现过程。
· 另外,如果S中包含一个与M匹配的非静态公共成员声明,那么这个成员就是I.M 的实现过程。
如果执行过程不能为在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就被允许有附加的访问符)。
接口映射算法中值得注意的地方是:
· 当已经确定了可以实现接口成员的类或结构成员时,显式接口成员实现程序比同一个类和结构中的其他成员有更高的优先级。
· 私有的、保护的合静态成员不参与接口映射。
在例子中
interface ICloneable
{
object Clone();
}
class C: ICloneable
{
object ICloneable.Clone() {...}
public object Clone() {}
}
C的成员ICloneable.Clone变成ICloneable 中的Clone 的实现程序,因为显式接口成员实现程序比其他成员有更高的优先级。
如果类或结构实现了两个和更多包含有相同的名称、类型和参数类型的成员的接口,那么把那些接口成员中的每个都映射到单独的类或结构成员是可以的。例如
interface IControl
{
void Paint();
}
interface IForm
{
void Paint();
}
class Page: IControl, IForm
{
public void Paint() {...}
}
这里,IControl 和 IForm 的Paint方法都映射到Page中的Paint方法上。当然这两个方法有分立的显式接口成员实现程序也是可以的。
如果一个类或结构实现了一个包含隐藏成员的接口,那么其中一些成员就需要通过显式接口成员实现程序来实现。例如
interface IBase
{
int P { get; }
}
interface IDerived: IBase
{
new int P();
}
这个接口的一个实现程序至少需要一个显式接口成员实现程序,并且要使用下面的一种形式
class C: IDerived
{
int IBase.P { get {...} }
int IDerived.P() {...}
}
class C: IDerived
{
public int P { get {...} }
int IDerived.P() {...}
}
class C: IDerived
{
int IBase.P { get {...} }
public int P() {...}
}
当一个类实现多个有相同基本接口的接口,那里就只能有一个基本接口的实现程序。在例子中
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 ,可以有分立的实现程序,IControl 被ITextBox 继承,并且IControl 也被IListBox 继承。实际上,不要求这些接口都相同。反之,ITextBox 和 IListBox 的实现程序共享相同的Icontrol的实现程序,而ComboBox 被认为实现了三个接口IControl, ITextBox, 和 IlistBox。
基本类的成员参与了接口映射。在例子中
interface Interface1
{
void F();
}
class Class1
{
public void F() {}
public void G() {}
}
class Class2: Class1, Interface1
{
new public void G() {}
}
Class1中的方法F 用于Class2 的Interface1的实现成员中。
13.4.3 接口实现程序继承
一个类继承了所有被它的基本类提供的接口实现程序。
不通过显式的实现一个接口,一个派生类不能用任何方法改变它从它的基本类继承的接口映射。例如,在声明中
interface IControl
{
void Paint();
}
class Control: IControl
{
public void Paint() {...}
}
class TextBox: Control
{
new public void Paint() {...}
}
TextBox 中的方法Paint 隐藏了Control中的方法Paint ,但是没有改变从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 继承的类可以通过覆盖方法PaintControl 来对IControl.Paint 的实现程序进行特殊化。
13.4.4 接口重新实现程序
一个继承了一个接口实现程序的类,可以通过在基本类列表中包含一个接口来重新实现这个接口。
一个接口的重新实现程序与一个接口的初始化实现程序有相同的接口映射规则。因此,继承的接口映射不会对为接口的重新实现程序建立的接口映射有任何影响。例如在声明中
interface IControl
{
void Paint();
}
class Control: IControl
{
void IControl.Paint() {...}
}
class MyControl: Control, IControl
{
public void Paint() {}
}
Control 把IControl.Paint 映射到Control.IControl.Paint 中的事实不会影响在MyControl 中的重新实现程序,它把IControl.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() {}
}
这里,Derived中的重新实现程序IMethods 把接口方法映射到Derived.F, Base.IMethods.G, Derived.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.F 映射到D.F。
13.4.5 抽象类和接口
与非抽象类相同,一个抽象类必须为类的基本类列表中列出的接口的所有成员提供实现程序。但是,一个抽象类被允许把接口方法映射到抽象方法中。例如
interface IMethods
{
void F();
void G();
}
abstract class C: IMethods
{
public abstract void F();
public abstract void G();
}
这里, IMethods 的实现函数把F和G映射到抽象方法中,它们必须在从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派生的非抽象类要覆盖FF 和 GG, 因此提供了IMethods的实际实现程序。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值