文章目录
C# 6.0 本质论学习笔记 —— 第 7 章 接口
并非只能通过继承使用多态性,还可以通过接口使用。和抽象类不同,接口不包括任何实现。但和抽象类类似,接口也包含了一组成员,调用者可以认为这些成员已经实现。
接口实现关系是一种“能做”关系,接口定义了“实现接口的类型”和“使用接口的代码”之间的契约。实现接口的类型必须使用接口要求的签名来定义方法。
7.1 接口概述
接口可以将实现细节和提供服务完全隔离开。
- 接口的特点是不包含实现,也不包含数据。它的方法声明之中用分号代替了大括号。
- 字段(数据)不能在接口声明中出现,如果接口要求派生类包含特定数据,会声明属性而不是字段;由于没有属性的任何实现可以作为接口声明的一部分,所以属性不引用支持
字段。 - 接口的关键特点是描述了在实现该接口的类型中必须能够访问的成员。c#不允许为接口成员使用访问修饰符,所有成员自动声明成为公有成员。
- 接口声明要采用Pascal风格,并使用I作为前缀。
1 2 3 4 5 6 7 |
interface IFileCompression { void Compress(String targetFileName,String[] fileList); void Uncompress(String compressedFileName,String expandDirectoryName ) } |
7.2 通过接口实现多态性
表明一个类实现一个接口使用和继承相似的语法。但是必须要在类中自己针对接口的方法们提供自己的实现。
- 对于通过一个方法名调用方法,只取决于它是否实现了某个接口,这是c#中多态性的一种体现。
- 每个类都有自己对于接口方法的实现,将类转换成IListable就可以调用特定的实现。
7.3 接口实现
- 声明类以实现接口,类似于从基类派生 —— 接口的顺序任意,但是必须在基类之后。
- 一旦一个类声明自己要实现接口,接口的所有成员都必须实现。
- 抽象类允许提供接口成员的抽象实现,非抽象实现可在方法主体中引发NoImplementException异常,但是必须为成员提供一个实现。
- 接口永远不能实例化,不能使用new新建一个接口,所以接口没有构造器和终结器;只有实例化实现接口的类型,才能使用接口实例。
- 接口不能包含静态成员。
- 不可以为接口成员显式使用abstract修饰符。
7.3.1 显式成员实现
在类型中实现接口成员时有两种方式,显式和隐式。通过类型的公共成员实现接口成员是隐式的方法。
显式实现的方法只能通过接口本身调用。最典型的做法是将对象转型为接口,然后再调用这个实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Contact:PdaItem,IListable,Icomparable { //.. public int ComparaTo(object obj) { //implicit inplement } string[] IListable.ColumValues { //explicit inplement } } |
由于显式接口实现直接与接口关联,因此没有必要使用virtual,override,public等修饰符;另外,由于他们不被看成类的公共成员,所以使其成为public有误导的嫌疑。
7.3.2
- 隐式实现的成员只要求是公共的,而且签名和接口成员的签名相符。
- 接口成员的实现不需要override关键字或者其它任何表明该成员与接口关联的指示符。
- 隐式实现的接口成员可以不执行转型而直接实现。
- 使用virtual关键字表示改接口实现可以继承,不使用则代表其被密封。
7.3.3 显式接口实现和隐式接口实现的比较
关键不在于成员声明的语法,而在于通过类型的实例而不是接口访问成员的能力。
比较继承的“属于”,接口常用于建模“机制”。
- 显式接口是将“机制方面的考虑”和“模型方面的考虑”分隔开的一种技术:可以显式的区分在何时与模型沟通,何时处理实现机制。
- 一般来说,最好的做法是将一个类的公共层面声明成为全模型,尽量少的涉及无关的机制。但是诸如散列码之类的模型和机制的混合往往无法避免。
- 如果成员不是核心的类功能,设计成为显式实现接口更好。
- 接口成员名作为类成员不恰当的话,应该使用显式实现。
- 有相同签名的类成员时,使用接口的显式实现。
- 可以全部显式定义接口,以确保以后可以安全的变成隐式实现。
7.4 在实现类和接口之间转换
从实现类型向它的以实现接口之间的转换时隐式转换,可以不需要转型操作符,总是能转型成功;而接口向实现类型转型,因为未必总是实现了全部功能,因此需要一次显式的转型。
7.5 接口继承
- 一个接口可以从另一个接口派生,派生的接口将继承基接口的所有成员。但是如果显式的实现接口的继承基接口的成员,则必须通过基接口的声明实现。
- 即使实现类的接口是从基接口之中派生的,仍然可以明确自己要实现这两个接口。
- 以下代码片段并不改变类的接口实现,只是提供了更好的可读性。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class FileSetiingProvider:ISettingProvider,IReaderableSettingProvider { public void SetSetting(/*..*/) { //.. } public string GetSetting(/*..*/) { //.. } } |
由于接口实际上代表的是契约,接口之间的继承实际上代表着一份契约同时也遵守另外一份契约。
7.6 多接口继承
类可以实现多个接口,接口也能从多个接口继承。
7.7 接口上的拓展方法
拓展方法除了可以作用于类,还可以作用于接口。语法和作用于类的拓展方法相同。
- c#不仅允许对特定类型的实例添加拓展方法,还允许对特定类型的实例添加拓展方法。
- 对拓展方法的支持是实现LINQ的基础。
7.8 通过接口实现多继承
由于c#的类可以使用多个接口,所以可以通过之实现某种意义上的多继承。
- 对于属性,通过继承接口可以保证两个类中的成员具有相同的签名,但是仍然无法继承实现。
- 对于方法,可以通过为接口定义对应方法的拓展实现,从而对实现也进行继承。实现该拥有相关拓展方法的接口的方法,即使没有成员,也会有拓展方法的默认实现——任何实例的实现都要优先于具有相同静态签名的拓展方法。
7.9 版本控制
如果一个组件或者应用供其他开发者使用,那么在创建它的新版本时不应当改动接口:接口意味着契约,改动接口相当于改动契约。不要为已交付的接口添加成员。
拓展接口可以选择从接口继承它生成新的接口。
7.10 接口和类的比较
接口引入了新的类型(少数不对终极基类System.Object拓展的基类之一),他不能实例化,实现必须实现所有成员,修改会破坏版本兼容性,不能声明字段,自动被看成抽象成员,不能包含实现,可以“多继承”。
- 一般优先考虑类而非接口。
- 可以用抽象类将契约和实现分开。
7.11 接口与特性的比较
- 避免使用无成员的接口标记,而是使用特性(attributes)。