第 24 节 · 第三部分 面向对象

接口(Interface)

接口只规定有哪些成员(是什么),实现接口的类负责写出具体怎么做。

时长 6:55授课计划第 9 周

学完这一节,你能

讲义

跟着视频的顺序写。每行代码都有注释,代码和运行结果都是实际运行得到的。

1接口是什么

上一节用 operator 让自己的类型也能相加;这一节解决另一个问题:怎么规定几个不同的类必须有同样的方法。答案是接口。

接口是一份语法合同:它只写“是什么”(要有哪些成员、叫什么名字),“怎么做”交给实现接口的类或结构体去写。对接口我们说类“实现”了它,不说“派生”。接口的成员可以是方法、属性、事件,还可以是索引器(属性、事件、索引器后面几章会讲)。

视频里把它比成插座测试台:接口是一块只开插孔、没有电路的插座板;实现接口的类是插件模块,插针和电路才是真正干活的部分;插针对准插孔才插得进去,按下按钮就是调用一次方法。

C# 8 以前接口里只有声明;从 C# 8 起接口方法也能带默认实现(见后文),教程“接口不实现任何功能”的说法已过时。

2定义接口

C# · 第 3-7 行
3interface IMyInterface// 用 interface 声明接口,名字习惯以大写 I 开头
4{// IMyInterface 接口开始
5 // 接口成员
6 void MethodToImplement();// 只有方法头:无参数、无返回值,直接以分号结尾,没有方法体
7}// IMyInterface 接口结束

接口前面什么都不写,默认是 internal(只在同一个程序集里能用;程序集:一个项目编译出来的 .exe 或 .dll,后面会讲),默认公开的是接口里的成员(教程说接口默认 public,不准确)。加两行查一查:

C# · 第 15-16 行
15Console.WriteLine(typeof(IMyInterface).IsPublic);// typeof 取到接口类型,IsPublic 问它是不是公开的
16Console.WriteLine(typeof(IMyInterface).GetMethod("MethodToImplement").IsPublic);// GetMethod 找到接口里的方法,再问这个成员是不是公开的
运行结果
MethodToImplement() called.
False
True

第一行是方法本身的输出;False 说明接口本身不是公开的,True 说明接口成员默认公开。GetMethod 那一行编译时会有一条警告(找不到方法时会得到 null),这里方法一定存在,可以不管:

编译警告
Program.cs(16,27): warning CS8602: 解引用可能出现空引用。

3实现接口

类名后面加冒号再写接口名,写法像继承,这里叫“实现接口”;类里必须写出 MethodToImplement。

C# · 第 9 行
9class InterfaceImplementer : IMyInterface// 冒号后面是接口:InterfaceImplementer 实现了 IMyInterface

Main 里先 new 一个对象,再调用这个方法:

C# · 第 13-14 行
13InterfaceImplementer iImp = new InterfaceImplementer();// new 一个对象 iImp
14iImp.MethodToImplement();// 调用它实现的方法

类里写出的方法:

C# · 第 17-20 行
17public void MethodToImplement()// 写出接口要求的方法:名字一致,而且是 public
18{// MethodToImplement 开始
19 Console.WriteLine("MethodToImplement() called.");// 打印一行字,这里才是真正干活的代码
20}// MethodToImplement 结束
运行结果
MethodToImplement() called.

名字必须一致

故意少写一个字母 e:

C# · 第 17 行
17public void MethodToImplment()// 少了一个 e:对编译器来说这是另一个新方法
编译报错
Program.cs(9,30): error CS0535: “InterfaceImplementer”不实现接口成员“IMyInterface.MethodToImplement()”

接口要的那个方法还是没有,所以报错指向第 9 行类名后面的 IMyInterface。

必须是 public

C# · 第 17 行
17void MethodToImplement()// 去掉了 public,类里的方法默认是 private
编译报错
Program.cs(9,30): error CS0737: “InterfaceImplementer”不实现接口成员“IMyInterface.MethodToImplement()”。“InterfaceImplementer.MethodToImplement()”无法实现接口成员,因为它不是公共的。

接口成员默认公开,实现它的方法也得公开。

接口不能 new

C# · 第 13 行
13IMyInterface x = new IMyInterface();// 想直接 new 一个接口
编译报错
Program.cs(13,26): error CS0144: 无法创建抽象类型或接口“IMyInterface”的实例

4接口继承

C# · 第 3-11 行
3interface IParentInterface// 父接口 IParentInterface
4{// IParentInterface 接口开始
5 void ParentInterfaceMethod();// 父接口里的一个方法
6}// IParentInterface 接口结束
7
8interface IMyInterface : IParentInterface// 冒号后写父接口:IMyInterface 继承了 IParentInterface
9{// IMyInterface 接口开始
10 void MethodToImplement();// 自己的方法;加上继承来的,一共两个
11}// IMyInterface 接口结束

实现 IMyInterface 的类,两个方法都必须写。

C# · 第 17-19 行
17InterfaceImplementer iImp = new InterfaceImplementer();// 创建实现类的对象
18iImp.MethodToImplement();// 第一行输出:自己接口的方法
19iImp.ParentInterfaceMethod();// 第二行输出:父接口的方法
查看完整代码(31 行)
C#
1using System;// 引入 System 命名空间,Console 在这里面
2
3interface IParentInterface// 父接口 IParentInterface
4{// IParentInterface 接口开始
5 void ParentInterfaceMethod();// 父接口要求的方法
6}// IParentInterface 接口结束
7
8interface IMyInterface : IParentInterface// IMyInterface 继承 IParentInterface
9{// IMyInterface 接口开始
10 void MethodToImplement();// IMyInterface 自己要求的方法
11}// IMyInterface 接口结束
12
13class InterfaceImplementer : IMyInterface// 实现 IMyInterface,也就要实现父接口的成员
14{// InterfaceImplementer 类开始
15 static void Main()// 程序入口 Main 方法
16 {// Main 开始
17 InterfaceImplementer iImp = new InterfaceImplementer();// 创建对象 iImp
18 iImp.MethodToImplement();// 调用自己接口的方法
19 iImp.ParentInterfaceMethod();// 调用父接口的方法
20 }// Main 结束
21
22 public void MethodToImplement()// 实现 IMyInterface 的方法
23 {// MethodToImplement 开始
24 Console.WriteLine("MethodToImplement() called.");// 打印第一行
25 }// MethodToImplement 结束
26
27 public void ParentInterfaceMethod()// 实现 IParentInterface 的方法
28 {// ParentInterfaceMethod 开始
29 Console.WriteLine("ParentInterfaceMethod() called.");// 打印第二行
30 }// ParentInterfaceMethod 结束
31}// InterfaceImplementer 类结束
运行结果
MethodToImplement() called.
ParentInterfaceMethod() called.

如果类里只写 MethodToImplement、漏写父接口的 ParentInterfaceMethod,同样报 CS0535,报错指向类声明这一行:

C# · 第 13 行
13class InterfaceImplementer : IMyInterface// 类体里只写了 MethodToImplement,少了 ParentInterfaceMethod
编译报错
Program.cs(13,30): error CS0535: “InterfaceImplementer”不实现接口成员“IParentInterface.ParentInterfaceMethod()”

5默认实现(C# 8 起)

从 C# 8 开始,接口方法可以直接写方法体,叫默认实现,实现类一个字都不用加。

C# · 第 8-11 行
8void SayHello()// 接口里的方法带了方法体
9{// SayHello 开始
10 Console.WriteLine("SayHello() 默认实现");// 默认实现:打印一行字
11}// SayHello 结束
C# · 第 18-20 行
18IMyInterface iImp = new InterfaceImplementer();// 变量类型写成接口,对象还是实现类
19iImp.MethodToImplement();// 调用类里写的方法
20iImp.SayHello();// 类里没写 SayHello,执行的是接口里的默认实现
运行结果
MethodToImplement() called.
SayHello() 默认实现

默认实现只能通过接口类型的变量调用,变量写成类的类型就找不到:

C# · 第 18-20 行
18InterfaceImplementer iImp = new InterfaceImplementer();// 变量类型是类,不是接口
19iImp.MethodToImplement();// 这一行没问题
20iImp.SayHello();// 类里没有 SayHello,编译报错
编译报错
Program.cs(20,14): error CS1061: “InterfaceImplementer”未包含“SayHello”的定义,并且找不到可接受第一个“InterfaceImplementer”类型参数的可访问扩展方法“SayHello”(是否缺少 using 指令或程序集引用?)

6接口和抽象类

相同点:都不能直接 new。抽象类可以派生出具体的类,由它们去干活。区别有三条:

  1. 字段:抽象类可以有字段,接口里不能有实例字段。
C# · 第 7 行
7int count;// 在接口里写一个实例字段
编译报错
Program.cs(7,9): error CS0525: 接口不能包含实例字段
  1. 方法体:抽象类一直能写,接口要从 C# 8 开始才能写。
  1. 继承个数:一个类只能继承一个基类,写两个就报错;但可以同时实现好几个接口。
C# · 第 3-11 行
3abstract class ParentBase// 抽象类 ParentBase,里面什么也没写
4{// ParentBase 类开始
5}// ParentBase 类结束
6
7abstract class MyBase// 抽象类 MyBase,同样是空的
8{// MyBase 类开始
9}// MyBase 类结束
10
11class InterfaceImplementer : MyBase, ParentBase// 冒号后写了两个基类
编译报错
Program.cs(11,38): error CS1721: 类“InterfaceImplementer”不能具有多个基类:“MyBase”和“ParentBase”

把接口继承的例子拆开,两个接口互不继承,类名后面写两个接口,输出和上面接口继承的例子一样,还是那两行:

C# · 第 8-13 行
8interface IMyInterface// 这次 IMyInterface 不再继承父接口
9{// IMyInterface 接口开始
10 void MethodToImplement();// 只有自己的一个方法
11}// IMyInterface 接口结束
12
13class InterfaceImplementer : IMyInterface, IParentInterface// 用逗号隔开,同时实现两个接口

教程说,抽象类多用在只有少数方法要交给派生类实现的时候;要让互不相干的几个类都具备同一种能力,就用接口。

7小结

  • 接口只规定有哪些成员,实现接口的类负责写出具体怎么做。
  • 实现接口要把成员一个不少地写出来,名字一致,还得是 public。
  • 接口可以继承接口;从 C# 8 起,接口方法还能带默认实现,只能通过接口类型的变量调用。
  • 一个类只能继承一个基类,却能实现多个接口。

下一节:两个人各写了一个叫 Rectangle 的类,放进同一个程序,编译器怎么分清是哪一个?

检查题

5 道,选完马上告诉你对不对。

  1. 1class InterfaceImplementer : IMyInterface 里写的方法叫 MethodToImplment(少一个 e),会怎样?

  2. 2实现接口的方法前面不写 public,会怎样?

  3. 3IMyInterface 继承了 IParentInterface,实现 IMyInterface 的类只写了 MethodToImplement,会怎样?

  4. 4接口里的 SayHello 带了方法体,实现类没写 SayHello,IMyInterface iImp = new InterfaceImplementer(); iImp.SayHello(); 会怎样?

  5. 5下面哪一条是接口和抽象类的区别?