1接口是什么
上一节用 operator 让自己的类型也能相加;这一节解决另一个问题:怎么规定几个不同的类必须有同样的方法。答案是接口。
接口是一份语法合同:它只写“是什么”(要有哪些成员、叫什么名字),“怎么做”交给实现接口的类或结构体去写。对接口我们说类“实现”了它,不说“派生”。接口的成员可以是方法、属性、事件,还可以是索引器(属性、事件、索引器后面几章会讲)。
视频里把它比成插座测试台:接口是一块只开插孔、没有电路的插座板;实现接口的类是插件模块,插针和电路才是真正干活的部分;插针对准插孔才插得进去,按下按钮就是调用一次方法。
C# 8 以前接口里只有声明;从 C# 8 起接口方法也能带默认实现(见后文),教程“接口不实现任何功能”的说法已过时。
2定义接口
| 3 | interface IMyInterface | // 用 interface 声明接口,名字习惯以大写 I 开头 |
| 4 | { | // IMyInterface 接口开始 |
| 5 | // 接口成员 | |
| 6 | void MethodToImplement(); | // 只有方法头:无参数、无返回值,直接以分号结尾,没有方法体 |
| 7 | } | // IMyInterface 接口结束 |
接口前面什么都不写,默认是 internal(只在同一个程序集里能用;程序集:一个项目编译出来的 .exe 或 .dll,后面会讲),默认公开的是接口里的成员(教程说接口默认 public,不准确)。加两行查一查:
| 15 | Console.WriteLine(typeof(IMyInterface).IsPublic); | // typeof 取到接口类型,IsPublic 问它是不是公开的 |
| 16 | Console.WriteLine(typeof(IMyInterface).GetMethod("MethodToImplement").IsPublic); | // GetMethod 找到接口里的方法,再问这个成员是不是公开的 |
MethodToImplement() called. False True
第一行是方法本身的输出;False 说明接口本身不是公开的,True 说明接口成员默认公开。GetMethod 那一行编译时会有一条警告(找不到方法时会得到 null),这里方法一定存在,可以不管:
Program.cs(16,27): warning CS8602: 解引用可能出现空引用。
3实现接口
类名后面加冒号再写接口名,写法像继承,这里叫“实现接口”;类里必须写出 MethodToImplement。
| 9 | class InterfaceImplementer : IMyInterface | // 冒号后面是接口:InterfaceImplementer 实现了 IMyInterface |
Main 里先 new 一个对象,再调用这个方法:
| 13 | InterfaceImplementer iImp = new InterfaceImplementer(); | // new 一个对象 iImp |
| 14 | iImp.MethodToImplement(); | // 调用它实现的方法 |
类里写出的方法:
| 17 | public void MethodToImplement() | // 写出接口要求的方法:名字一致,而且是 public |
| 18 | { | // MethodToImplement 开始 |
| 19 | Console.WriteLine("MethodToImplement() called."); | // 打印一行字,这里才是真正干活的代码 |
| 20 | } | // MethodToImplement 结束 |
MethodToImplement() called.
名字必须一致
故意少写一个字母 e:
| 17 | public void MethodToImplment() | // 少了一个 e:对编译器来说这是另一个新方法 |
Program.cs(9,30): error CS0535: “InterfaceImplementer”不实现接口成员“IMyInterface.MethodToImplement()”
接口要的那个方法还是没有,所以报错指向第 9 行类名后面的 IMyInterface。
必须是 public
| 17 | void MethodToImplement() | // 去掉了 public,类里的方法默认是 private |
Program.cs(9,30): error CS0737: “InterfaceImplementer”不实现接口成员“IMyInterface.MethodToImplement()”。“InterfaceImplementer.MethodToImplement()”无法实现接口成员,因为它不是公共的。
接口成员默认公开,实现它的方法也得公开。
接口不能 new
| 13 | IMyInterface x = new IMyInterface(); | // 想直接 new 一个接口 |
Program.cs(13,26): error CS0144: 无法创建抽象类型或接口“IMyInterface”的实例
4接口继承
| 3 | interface IParentInterface | // 父接口 IParentInterface |
| 4 | { | // IParentInterface 接口开始 |
| 5 | void ParentInterfaceMethod(); | // 父接口里的一个方法 |
| 6 | } | // IParentInterface 接口结束 |
| 7 | | |
| 8 | interface IMyInterface : IParentInterface | // 冒号后写父接口:IMyInterface 继承了 IParentInterface |
| 9 | { | // IMyInterface 接口开始 |
| 10 | void MethodToImplement(); | // 自己的方法;加上继承来的,一共两个 |
| 11 | } | // IMyInterface 接口结束 |
实现 IMyInterface 的类,两个方法都必须写。
| 17 | InterfaceImplementer iImp = new InterfaceImplementer(); | // 创建实现类的对象 |
| 18 | iImp.MethodToImplement(); | // 第一行输出:自己接口的方法 |
| 19 | iImp.ParentInterfaceMethod(); | // 第二行输出:父接口的方法 |
查看完整代码(31 行)
| 1 | using System; | // 引入 System 命名空间,Console 在这里面 |
| 2 | | |
| 3 | interface IParentInterface | // 父接口 IParentInterface |
| 4 | { | // IParentInterface 接口开始 |
| 5 | void ParentInterfaceMethod(); | // 父接口要求的方法 |
| 6 | } | // IParentInterface 接口结束 |
| 7 | | |
| 8 | interface IMyInterface : IParentInterface | // IMyInterface 继承 IParentInterface |
| 9 | { | // IMyInterface 接口开始 |
| 10 | void MethodToImplement(); | // IMyInterface 自己要求的方法 |
| 11 | } | // IMyInterface 接口结束 |
| 12 | | |
| 13 | class 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,报错指向类声明这一行:
| 13 | class InterfaceImplementer : IMyInterface | // 类体里只写了 MethodToImplement,少了 ParentInterfaceMethod |
Program.cs(13,30): error CS0535: “InterfaceImplementer”不实现接口成员“IParentInterface.ParentInterfaceMethod()”
5默认实现(C# 8 起)
从 C# 8 开始,接口方法可以直接写方法体,叫默认实现,实现类一个字都不用加。
| 8 | void SayHello() | // 接口里的方法带了方法体 |
| 9 | { | // SayHello 开始 |
| 10 | Console.WriteLine("SayHello() 默认实现"); | // 默认实现:打印一行字 |
| 11 | } | // SayHello 结束 |
| 18 | IMyInterface iImp = new InterfaceImplementer(); | // 变量类型写成接口,对象还是实现类 |
| 19 | iImp.MethodToImplement(); | // 调用类里写的方法 |
| 20 | iImp.SayHello(); | // 类里没写 SayHello,执行的是接口里的默认实现 |
MethodToImplement() called. SayHello() 默认实现
默认实现只能通过接口类型的变量调用,变量写成类的类型就找不到:
| 18 | InterfaceImplementer iImp = new InterfaceImplementer(); | // 变量类型是类,不是接口 |
| 19 | iImp.MethodToImplement(); | // 这一行没问题 |
| 20 | iImp.SayHello(); | // 类里没有 SayHello,编译报错 |
Program.cs(20,14): error CS1061: “InterfaceImplementer”未包含“SayHello”的定义,并且找不到可接受第一个“InterfaceImplementer”类型参数的可访问扩展方法“SayHello”(是否缺少 using 指令或程序集引用?)
6接口和抽象类
相同点:都不能直接 new。抽象类可以派生出具体的类,由它们去干活。区别有三条:
- 字段:抽象类可以有字段,接口里不能有实例字段。
| 7 | int count; | // 在接口里写一个实例字段 |
Program.cs(7,9): error CS0525: 接口不能包含实例字段
- 方法体:抽象类一直能写,接口要从 C# 8 开始才能写。
- 继承个数:一个类只能继承一个基类,写两个就报错;但可以同时实现好几个接口。
| 3 | abstract class ParentBase | // 抽象类 ParentBase,里面什么也没写 |
| 4 | { | // ParentBase 类开始 |
| 5 | } | // ParentBase 类结束 |
| 6 | | |
| 7 | abstract class MyBase | // 抽象类 MyBase,同样是空的 |
| 8 | { | // MyBase 类开始 |
| 9 | } | // MyBase 类结束 |
| 10 | | |
| 11 | class InterfaceImplementer : MyBase, ParentBase | // 冒号后写了两个基类 |
Program.cs(11,38): error CS1721: 类“InterfaceImplementer”不能具有多个基类:“MyBase”和“ParentBase”
把接口继承的例子拆开,两个接口互不继承,类名后面写两个接口,输出和上面接口继承的例子一样,还是那两行:
| 8 | interface IMyInterface | // 这次 IMyInterface 不再继承父接口 |
| 9 | { | // IMyInterface 接口开始 |
| 10 | void MethodToImplement(); | // 只有自己的一个方法 |
| 11 | } | // IMyInterface 接口结束 |
| 12 | | |
| 13 | class InterfaceImplementer : IMyInterface, IParentInterface | // 用逗号隔开,同时实现两个接口 |
教程说,抽象类多用在只有少数方法要交给派生类实现的时候;要让互不相干的几个类都具备同一种能力,就用接口。
7小结
- 接口只规定有哪些成员,实现接口的类负责写出具体怎么做。
- 实现接口要把成员一个不少地写出来,名字一致,还得是
public。 - 接口可以继承接口;从 C# 8 起,接口方法还能带默认实现,只能通过接口类型的变量调用。
- 一个类只能继承一个基类,却能实现多个接口。
下一节:两个人各写了一个叫 Rectangle 的类,放进同一个程序,编译器怎么分清是哪一个?
