接口定义了一份契约,实现了接口的类或结构就意味着遵守接口定义的契约。接口只允许包含方法、属性、索引器和事件的签名,请注意,这里只允许包含签名,而不能包含实现。另外,接口不能包含常量、字段、运算符、实例构造函数、析构函数以及任何静态成员。
由于接口中不包含成员实现,因此接口中定义的成员必须在实现该接口的类或结构中提供成员实现。因此,一个接口的不同实现类可以有多个不同的实现方式。因此,当外界依赖的是接口,而不是实现类的时候,可以轻易地调换实现该接口的实现类,而无须改变依赖于该接口的代码,这就是业界为什么推崇"面向接口编程"的原因。
实现类不能选择实现接口中的某些成员,而不去实现另外一些成员,它必须全部、毫无保留地实现接口的所有成员,无论是方法、属性、索引器还是事件。除此以外,实现类还可以定义自己的成员,由于这些成员没有在接口中进行定义,因此当通过接口的引用进行访问的时候,这些成员将不可见。
那么,我们来看看接口具有哪些特点:
❑接口可以从一个或多个基接口继承,一个类或结构也可以实现多个接口,如图12-1所示;

❑接口只包含方法、属性、索引器和事件的签名,不能包含实现;
❑接口不能包含字段、常量、运算符、实例构造函数、析构函数以及任何静态成员;
❑接口的成员默认是公共的(public),且不可以再显式声明为public,否则将产生编译异常:修饰符"public"对该项无效。
❑接口不能被实例化,从接口类型编译后生成的CIL代码可以看到,接口的声明是抽象(abstract)的,如图12-2所示;

❑实现接口的类必须实现接口的所有成员,不管是方法、属性、索引器还是事件。
12.1 定义接口
首先,我们要学习如何定义一个接口。接口的定义非常类似于类的定义,只是把class关键字换成interface关键字,然而,接口的成员只包含签名,不包含内容,且不能包含任何访问修饰符(默认为public级别),需要说明的是必须以分号结尾,语法如图12-3所示。

下面,我们对接口定义中的几个关键部分进行说明,如表12-1所示。

我们知道,接口中可以包含方法、属性、索引器和事件的签名,不包含实现,我们接下来就通过一段示例代码来展示这些合法的成员是如何在接口中进行声明的,如代码清单12-1所示。
代码清单12-1 接口成员示例
cs
interface Sample
{
void SayHello();//方法签名
int Age{get;set;}//属性签名
EventHandler SizeChanged();//事件签名
int this[int index]{get;set;}//索引器签名
}
在上述代码中,我们分别定义了一个方法,一个属性,一个事件,以及一个索引器。其中,属性的和索引器的签名必须包含完整的get和set访问器,这点需要特别注意。
接下来,我们来学习如何声明和实现接口。
12.2 声明和实现接口
先来定义两个接口,每个都仅定义了一个方法成员,如代码清单12-2所示。
代码清单12-2 接口的定义
cs
interface ISample
{
void Method1();
}
interface ISample2
{
void Method2();
}
我们在上述代码中定义了两个接口:ISample和ISample2。在接口的定义中省略了访问修饰符,那么意味着它们当前的访问级别是internal,即只能被同程序集的类"看到"并实现,而在程序集外根本无法"看到"。大家注意到了,这两个接口名称分别为ISample和ISample2。说到这里,顺便介绍一下接口的命名规则。接口的命名一般使用名词或名词短语,或者描述行为的形容词,尽可能少用缩写,不要使用下划线字符,大小写采用Pascal风格,即首字母和后面的每个单词的首字母都大写,其他字母小写。大家可能注意到了接口名字前的前缀I,它表示这个类型是一个接口。下面是几个合乎规范的例子:
❑IComponent(描述性名词)
❑IUserService(名词短语)
❑IDisposable(形容词)
请注意,上述接口命名的规则只是一个推荐规则,并非标准,具体规则应以您所在单位或者组织的规定为准。
截至目前,我们已经定义了两个接口,下一步要学习如何实现一个接口。从一定意义上说,接口只是表示其所定义成员的一种存在性,它是一个契约。
实现一个接口的语法如图12-4所示。

接下来,我们对定义接口的几个关键部分进行说明,如表12-2所示。

我们继续举例说明,先定义两个类,分别实现接口ISample和ISample2,如代码清单12-3所示。
代码清单12-3 实现接口
cs
class ClassOne : ISample
{
public void Method1()
{
System.Console.WriteLine("from ClassOne.Method1()");
}
public void Method2()
{
System.Console.WriteLine("from ClassOne.Method2()");
}
}
class ClassTwo : ISample
{
public void Method1()
{
System.Console.WriteLine("from ClassTwo.Method1()");
}
}
上述代码的实现接口如图12-5所示。

关于代码清单12-3的说明如表12-3所示。

接下来,我们来看看如何调用这两个实现类,如代码清单12-4所示。
代码清单12-4 实例化接口的实现类
cs
class ClassExample
{
public static void Main()
{
ISample sample1 = new ClassOne();
ISample sample2 = new ClassTwo();
sample1.Method1();
sample2.Method1();
}
}
这段代码的执行结果如下:
from ClassOne.Method1()
from ClassTwo.Method1()
在上述代码中,要特别关注的是声明了两个类型为ISample的变量sample1和sample2,它们分别使用ClassOne和ClassTwo类进行了初始化。这说明接口类型的变量可以指向任何实现了这个接口的类的实例,这样固然很好,可以灵活地指定某个实现类,而不需要改变依赖这个接口的代码。但这样一来就只能通过这些接口类型的引用调用接口的方法,如果想要调用某个实现类中的自定义的、不在接口中的方法,就需要把引用强制类型为合适的类型。下面通过一个例子进行说明。
在ClassOne类中添加一个Method2方法,该方法的定义并未出现在接口ISample中,因此它属于ClassOne类,通过ISample接口的引用将无法看到访问到它,必须将ISample接口的引用向下转型到ClassOne类型才能进行访问,如代码清单12-5所示。代码清单12-5 通过向下转型调用接口实现类的专属方法示例
cs
class ClassOne : ISample
{
public void Method1()//定义于ISample接口中的方法
{
System.Console.WriteLine("from ClassOne.Method1()");
}
public void Method2()//专属于ClassOne类的方法
{
System.Console.WriteLine("from ClassOne.Method2()");
}
}
class ClassTwo : ISample
{
public void Method1()//定义于ISample接口中的方法
{
System.Console.WriteLine("from ClassTwo.Method1()");
}
}
class ClassExample
{
public static void Main()
{
ISample sample1 = new ClassOne();
ISample sample2 = new ClassTwo();
sample1.Method1();
sample2.Method1();
//将ISample类型的引用向下转型到ClassOne类型的引用
ClassOne clsOne = (ClassOne)sample1;
clsOne.Method2();
}
}
12.3 基类中的实现作为接口实现
在实现类的基类中提供所实现接口中成员的实现也是可以的。我们使用一个例子来说明,先声明一个接口ISample,然后声明一个类ClassTwo,它继承自类ClassOne,并实现了接口ISample。其中,ISample接口所要求实现的Method1方法并未在ClassTwo类中实现,而是在它的基类ClassOne中提供了实现,如代码清单12-6所示。
代码清单12-6 基类中的实现作为接口实现
cs
interface ISample
{
void Method1();
}
class ClassOne
{
public void Method1()
{
System.Console.WriteLine("from ClassOne.ISample.Method1()");
}
}
class ClassTwo : ClassOne, ISample
{
//没有定义任何成员
}
class ClassExample
{
public static void Main()
{
ISample sample = new ClassTwo();
sample.Method1();
}
}
上述代码的运行结果如下:
from ClassOne.ISample.Method1()
上述代码如图12-6所示。

12.4 实现多个接口
在C#中一个类不能从多个类继承,但可以实现多个基接口,而一个接口可以从多个接口继承。在12.2节我们学习了实现单个接口的情况,本节将学习一个类实现多个接口。顾名思义,实现多个接口就意味着,在实现类中要实现所有实现的接口规定的所有成员,也意味着对于同一个类,可以使用它实现的任意接口类型来引用它的实例,但使用不同的接口类型引用,直接影响该类型的可用成员。例如类A实现了接口Interface1和Interface2,当使用Interface1的类型引用时,只有Interface1定义的成员可见并可用;使用Interface2的类型引用时也只能调用Interface2接口定义的成员。
下面来看一个例子,如代码清单12-7所示。在这个例子中,先定义两个接口,再定义一个类实现它们,最后再分别通过两种接口类型的引用调用类的实例。代码清单12-7 实现多个接口
cs
interface ISample
{
void Method1();
}
interface ISample2
{
void Method2();
}
class ClassOne : ISample, ISample2
{
public void Method1()
{
System.Console.WriteLine("from ClassOne.Method1()");
}
public void Method2()
{
System.Console.WriteLine("from ClassOne.Method2()");
}
}
class ClassExample
{
public static void Main()
{
ISample sample1 = new ClassOne();
ISample2 sample2 = new ClassOne();
sample1.Method1();
sample2.Method2();
}
}
上述代码的执行结果如下:
from ClassOne.Method1()
from ClassOne.Method2()
关于代码清单12-7的说明如表12-4所示。

上述代码使用图示说明,如图12-7所示。

12.5 实现具有相同成员的接口
因为类可以实现多个接口,有可能在两个或更多的接口中出现具有相同签名的成员。此时,可以提供一种实现,以供所有定义了该成员的接口共享,也可以显式地定义接口成员实现(详见12.6节)。
接下来,我们使用一个例子来说明。定义两个接口ISample和ISample2,它们都定义了方法Method1,两个方法的签名和返回值均相同。然后再定义一个类ClassOne,它实现了这两个接口,并在ClassOne类中提供了Method1方法的实现,如代码清单12-8所示。代码清单12-8 实现具有相同成员的接口
cs
interface ISample
{
void Method1();
}
interface ISample2
{
void Method1();
}
class ClassOne : ISample, ISample2
{
public void Method1()
{
System.Console.WriteLine("from ClassOne.ISample.Method1()");
}
}
class ClassExample
{
public static void Main()
{
ISample sample = new ClassOne();
ISample2 sample2 = new ClassOne();
sample.Method1();
sample2.Method1();
}
}
上述代码的运行结果如下:
from ClassOne.ISample.Method1()
from ClassOne.ISample.Method1()
上述代码的图示见图12-8。

12.6 显式接口成员实现
如果在要实现的多个接口中,存在有相同的成员,除了12.5节所讲的"提供一个共同的实现"以外,还可以分别实现,那么怎么区别哪个方法是哪个接口的呢?答案就是采用"限定接口名称"来声明。所谓"限定接口名称"就是"接口名.方法名"的样式,类似于我们使用的"命名空间.类名"的限定方式。接下来,我们通过一个例子来学习显式接口成员的实现。首先,声明两个接口,它们都定义了Method1方法,方法的签名相同。然后定义一个类ClassOne去实现这两个接口,这里的关键就是在ClassOne类中对两个接口中的Method1方法进行分别实现,如代码清单12-9所示。代码清单12-9 显示接口成员实现
cs
interface ISample
{
void Method1();
}
interface ISample2
{
void Method1();
}
class ClassOne : ISample, ISample2
{
void ISample.Method1()
{
System.Console.WriteLine("from ClassOne.ISample.Method1()");
}
void ISample2.Method1()
{
System.Console.WriteLine("from ClassOne.ISample2.Method1()");
}
}
class ClassExample
{
public static void Main()
{
ClassOne clsOne = new ClassOne();
ISample sample1 = (ISample)clsOne;
ISample2 sample2 = (ISample2)clsOne;
sample1.Method1();
sample2.Method1();
}
}
上述代码的运行结果如下:
from ClassOne.ISample.Method1()
from ClassOne.ISample2.Method1()
关于代码清单12-9的说明如表12-5所示。

图12-9演示了显式接口实现的方法。

12.7 调用显式接口成员实现
对于显式的接口成员实现,只能通过接口进行访问,除此以外都不能进行访问,即使在实现类中的其他方法也不能。我们以代码清单12-9为基础,尝试在实现类中定义一个新方法,然后在该方法中尝试访问显式定义的接口成员实现,如代码清单12-10所示。代码清单12-10 调用显式接口成员实现
cs
interface ISample
{
void Method1();
}
interface ISample2
{
void Method1();
}
class ClassOne : ISample, ISample2
{
void ISample.Method1()//显式接口实现
{
System.Console.WriteLine("from ClassOne.ISample.Method1()");
}
void ISample2.Method1()//显式接口实现
{
System.Console.WriteLine("from ClassOne.ISample2.Method1()");
}
public void NewMethod()
{
//Method1();//编译错误
//this.Method1();//编译错误
((ISample2)this).Method1();//通过接口访问
}
}
class ClassExample
{
public static void Main()
{
ClassOne clsOne = new ClassOne();
clsOne.NewMethod();
}
}
上述代码的运行结果为:
from ClassOne.ISample2.Method1()
关于代码清单12-10的说明如表12-6所示。

需要说明的是,在这种情况下,实现类的派生类也无法访问显式的接口成员实现,只能通过接口进行调用,为了说明此问题,我们定义了一个类ClassTwo,它派生自ClassOne,ClassTwo有一个Method3方法,在其中同样无法访问基类中显式定义的接口成员实现,如代码清单12-11所示。代码清单12-11 ClassTwo类的代码
cs
class ClassTwo:ClassOne
{
public void Method3()
{
//base.Method1();//编译错误
((ISample2)this).Method1();//正确,通过接口访问
}
}
12.8 接口的派生
和类一样,接口也可以从基接口派生,但和类的派生也有很大的不同:类不能继承多个类,而接口可以继承多个接口。如果要从多个接口继承,这些基接口之间只需使用逗号进行分隔即可。
接下来使用例子来说明。先声明两个接口ISample和ISample2,然后再声明一个接口ISample3,它继承自前面声明的两个接口ISample和ISample2,然后定义一个类ClassTree,它实现接口ISample3,如代码清单12-12所示。代码清单12-12 接口的派生
cs
interface ISample
{
void Method1();
}
interface ISample2
{
void Method2();
}
interface ISample3 : ISample, ISample2//从其他接口派生
{
void Method3();
}
class ClassTree : ISample3//需要实现所有接口定义的成员
{
public void Method1()//ISample接口中定义的成员
{
System.Console.WriteLine("from ClassTree.Method1()");
}
public void Method2()//ISample2接口中定义的成员
{
System.Console.WriteLine("from ClassTree.Method2()");
}
public void Method3()//ISample3接口中定义的成员
{
System.Console.WriteLine("from ClassTree.Method3()");
}
}
class ClassExample
{
public static void Main()
{
ClassTree clsTree = new ClassTree();
clsTree.Method1();
clsTree.Method2();
clsTree.Method3();
ISample sample1 = (ISample)clsTree;
ISample2 sample2 = (ISample2)clsTree;
ISample3 sample3 = (ISample3)clsTree;
sample1.Method1();
sample2.Method2();
//ISample3接口派生自ISample和ISample2
//因此ISample3接口继承了基接口定义的成员
sample3.Method1();
sample3.Method2();
sample3.Method3();
}
}
上述代码运行的结果为:
from ClassTree.Method1()
from ClassTree.Method2()
from ClassTree.Method3()
from ClassTree.Method1()
from ClassTree.Method2()
from ClassTree.Method1()
from ClassTree.Method2()
from ClassTree.Method3()