17.1 什么是接口
接口是表示一组函数成员而不实现成员的引用类型。其他类型——类和结构可以实现接口。
为了对接口有一些感性认识,让我们先来看一个已经定义好的接口。BCL声明了一个叫做IComarable的接口,它的声明如下面的代码所示。注意,接口主体中包含了一个CompareTo方法,它接受一个Object类型的参数。尽管方法有名字、参数和返回类型,却没有实现。实现被一个分号来替代了。
具体代码实现由类或结构来完成。要实现接,类或结构必须做两件事情:
- 必须在基类列表后面列出接口名称。
- 必须为接口提供每一个成员的实现。
17.2 声明接口
关于接口,需要知道的重要事项如下所示:
- 接口声明不包含数据成员。
- 接口声明只能包含如下类型的静态成员函数的声明:
- 方法
- 属性
- 事件
- 索引
- 这些函数成员的声明不能包含任何实现代码,而在每一个成员声明的主体后必须使用分号。
- 按照惯例,接口名称必须从大写的I开始(比如ISaveable)。
和类以及结构一样,接口声明还可以分隔成分部接口的声明。例:
interface IMyInterface1{
int DoStuff(int nvar1,long lVar2);double DoOtherStuff(string s,long x);
}
接口的访问性和接口成员的访问性之间有一些重要区别:
- 接口声明可以有任何的访问修饰符public、protected、internal或private。
- 然而,接口的成员是隐式public的,不允许有任何访问修饰符,包括public。
17.3 实现接口
只有类和结构才能实现接口。要实现接口,类或结构必须:
- 在基类列表中包括接口名称。
- 为每一个接口的成员提供实现。
例:
class MyClass :IMyInterface1{
int DoStuff(int nVar1,long iVar2){...} //实现代码double DoOtherStuff(string s,long x)
{...}//实现代码
}
关于实现接口,需要了解的重要事项如下:
- 如果类实现了接口,它必须实现接口的所有成员。
- 如果类从基类继承并实现接口,基类列表中的基类名称必须放在任何接口之前。
17.4 接口是引用类型
接口不仅仅是类或结构要实现的成员列表。它是一个引用类型。
我们不能直接通过类对象的成员访问接口。然而,我们可以通过把类对象引用强制转换为接口类型来获取指向接口的引用。一旦有了接口的引用,我们就可以使用点号来调用接口的方法。
IIfc1 ifc=(IIfc1)mc; //获取接口的引用
ifc.PrintOUt("interface"); //使用接口的引用调用方法
17.5 接口和as运算符
在前面的内容中,我经知道了可以使用强制运算符转换运算符来获取对象接口的引用,另一个更好的方式是使用as运算符。
如果我们尝试强制转换类对象引用为类未实现的接口的引用,强制转换操作会抛出一个异常。我们可以通过使用as运算符来避免这个问题。具体方法如下所示。
- 如果类实现了接口,表达式返回指向接口的引用。
- 如果类没有实现接口,表达式返回null而不是抛出异常。
所以要使用转换后的接口,需先判断是否为空。
17.6 实现多个接口
类或结构可以实现任意数量的接口。
所有实现的接口必须列在基类列表中并以逗号分隔(如果有基类名称,则在其之后)。
17.7 实现具有重复成员的接口
由于类可以实现任意数量的接口,有可能两个或多个接口成员都有相同的签名和返回类型。编译器如何处理这样的情况呢?答案是:如果一个类实现了多个接口,那么其中一些接口有相同签名和返回类型的成员。类可以实现单个成员来满足所有包含重复成员的接口。
17.8 多个接口的引用
我们已经在之前的内容中知道 了接口是引用类型,并且可以通过强制转换对象引用为接口类型的引用来获取一个指向接口的引用。如果类实现了多个接口,我们可以获取每一个的独立引用。
17.9 派生成员作为实现
实现接口的类型可以从它的基类继承实现的代码。
例如:
- IIfc1是一个具有PrintOut方法成员的接口。
- MyBaseClass包含了一个叫做PrintOut的方法,它和IIfc1的方法相匹配。
- Derived类有一个空的声明主体,但它派生自MyBaseClass,并在基类列表中包含了IIfc1。
- 即使Derived的声明主体是空的,基类中的代码还是能满足实现接口方法的需求。
17.10 显式成员的实现
在之前的内容中,我们已经看到单个类可以实现多个接口需要的所有成员。但是,如果我们希望为每一个接口单独实现该怎么做?在这种情况下,我们可以创建显式接口成员实现。显式接口成员实现有如下特性:
- 与所有接口实现相似,它被类和结构用来实现接口。
- 它使用限定接口名称来声明,由接口名称和成员名称以及它们中间的点分隔符构成。
如果有显式接口成员实现,类级别的实现是允许的,但不是必须的。显式了类或结构必须实现的方法。因此,我们可以有如下三种实现场景:
- 类级别实现
- 显式接口成员实现。
- 类级别和显式接口成员实现。
访问显式接口成员实现
显式接口成员实现只可能通过指向接口的引用来访问。也就是说,其他的类成员都不可以直接访问它们。
例如,如下代码演示了MyClass类的声明,它使用显式实现实现了IIfc1接口。注意,即使是MyClass的另一成员Method1,也不可以直接访问显式实现。
- Method1的前两行代码产生了编译错误,因为方法在尝试直接访问实现。
- 只有Method1的最后一行代码才可以编译,因此它强制转换当前对象的引用(this)为接口类型的引用,并使用这个指向接口的引用来周用显式接口的实现。
class Class1 :IIfc1 { void IIfc1.PrintOut(string s) { Console.WriteLine("IIfc1"); } public void method1() { PrintOut("...."); //编译错误 this.PrintOut("..."); //编译错误 ((IIfc1)this).PrintOut("..."); //调用方法 } }
这个限制对继承产生了重要问题。由于其他类不能直接访问显式接口成员实现,衍生类的成员也不能直接访问它们。它们必须总是通过接口的引用来访问。
17.11 接口可以继承接口
之前我们已经知道接口实现可以从基类被继承,而接口本身也可以从一个或多个接口继承。
- 要指定某个接口继承其他的接口,应在接口声明中把基接口以逗号分隔的列表形式放在接口名称的冒号之后,如下所示。
interIDataIO :IDataRetrieve,IDataStore{.....}
- 与类不同,它只在基类列表中只能有一个类名,而接口可以在基接口列表中有任意多个接口。
- 列表中的接口本身可以有被继承的接口。
- 结果接口包含它声明的所有接口和所有基接口的成员。