一. 接口的类型

接口是引用类型.因此从值类型赋值给接口是需要装箱的.如下所示:

 class Program
{
static void Main(string[] args)
{
ISay catSay = new Cat();
catSay.Say();
Console.Read();
}
} interface ISay
{
void Say();
}
struct Cat : ISay
{
public void Say()
{
Console.WriteLine("Cat Say!");
}
}

IL代码:

我们看到,这里有一个装箱的操作,而只有把值类型转到引用类型才会产生装箱操作,因而接口是引用类型.

二. 接口的定义

我们知道,在定义一个方法的时候,我们需要定义:

1.方法的签名:也就是这个方法的名称,参数与返回值

2.方法的实现:也就是这个方法具体的内容

      接口只是定义了一系列方法的签名,不包含方法的实现,且签名不能有任何修饰符.这意味着:

1.属性 事件 索引器也可定义入接口中,因为它们本质上都是方法,不能定义字段;

2.接口不可以定义任何构造器方法

下面是IConvertible的接口定义,它定义了很多类型转换的方法,但是并没有任何实现.

三. 接口的实现(单继承与多继承)

一个类可以同时继承m个基类(m=0,1)和n个接口(n≥0),同时类必须显式实现所有接口,接口不能继承基类,但是可以继承接口.

我们定义如下:

 interface ITest1
{
void Test1();
}
interface ITtest2
{
void Test2();
}
class Base
{
}

      单继承:我们先类或接口来看只继承一个基类或接口的情况.如下所示:

 class Test : Base//类继承类
class Test : ITest1//类继承接口
interface ITest1: ITtest2//接口继承接口
interface ITest1: Base//(错误)接口继承类

其实第4种情况我们也能理解:假设接口能继承类,那么我们接口是不是就有了方法的实现,前面我们说过,接口只能有方法的签名不能有方法的实现,两者就相悖了.

      多继承:我们再来看类或接口继承一个类型和多个接口的情况

 class Test : Base,ITest1,ITtest2//类继承1个基类和多个接口
interface ITtest3:ITest1,ITtest2//接口继承多个接口

使用接口的多继承,可以为一个类同时约束多个方法的实现,规定一个方法必须实现所有的的方法. 这既有好处,也不坏处.

      1:接口有什么好处?

拿我们最简单的例子来说,我们都有电脑,如果我们想为这台电脑升级一块硬盘,我们只需要更新一块符合这个电脑主板上硬盘的接口的新硬盘就可以了,我们不用更换整台电脑,也不用只更新特定厂商的硬盘. 因为所有的硬盘厂商都遵循着公共的接口标准,这样耦合就降低了.

其实,这样的例子生活中随处可见,如我们常用的USB接口,电源插座等等.

      2:接口继承的问题1

接口的坏处也显而易见,最明显的就是我们可能只需要实现一个接口中的一个方法,但被迫实现了接口中的其他方法,哪怕实现的方法体中没有任何代码.

      3:接口继承的问题2—重名问题

还有一个问题,就是多接口继承带过来的问题.

我们定义如下接口:

interface ITest1
{
void TestMethod ();
}
interface ITtest2
{
void TestMethod ();
}
class Test : ITest1, ITtest2
{
}

我们看到,ITest1和ITest2中有两个相同的方法,都是TestMethod (),Test类同时继承两个接口,那么问题来了,我们前面说过必须显式实现所有接口方法”,Test类该如何实现这两个同名方法?

C#提供了答案,即是它们可以共用一个实现,我姑且称之为共用实现,也可以分别各自实现,我们称之为显式实现.

在讲共用实现和显式实现之前,我们需要加一个基础知识的铺垫.

在CLR中,当一个类型加载进来时,会为这个类型建立一个方法列表,它包括以下内容:

1.这个类本身的方法记录

      2.从基类继承过来的虚方法记录

      3.从接口接口过来的虚方法记录

那么,在前面Test的例子中,Test的方法列表是什么?

1.TestMethod//Test类本身的方法

2.Ojbect(隐式继承的基类)的方法,如下

3.TestMethod// 继承自ITest1接口的虚方法

4.TestMethod// 继承自ITest2接口的虚方法

      共用实现

      C#编译器一比较,发现TestMethod这个方法的签名相同,也是显式public的,因为它认为ITest1和ITest2的接口方法和Test类本身的TestMethod方法完全一致.在生成元数据的时候,标明Test的TestMethod方法和ITest1的TestMethod方法和Test2的TestMethod方法三者都应该引用同一个实现.因而,下面这段代码的输出是:Hello

 class Test : ITest1, ITtest2
{
public void TestMethod()
{
Console.WriteLine("Hello");
}
}
static void Main(string[] args)
{
new Test().TestMethod();
Console.Read();
}

可不可以指定接口自己特定的实现?也就是说ITest1有自己的接口实现, ITest2有自己的接口实现, Test也有自己的同名实现方法.答案是可行的,这就是下面要说的显式实现.

      显式实现:在方法的名称前带上接口的名称,同时接口方法就默认为private,并且不能改变它的访问级别,调用时只能通过接口来调用,如下所示

 class Test : ITest1, ITtest2
{
public void TestMethod()
{
Console.WriteLine("Test");
}
void ITest1.TestMethod()
{
Console.WriteLine("Test1");
} void ITtest2.TestMethod()
{
Console.WriteLine("Test2");
}
}

如果要调用Test类本身的TestMethod()方法,我们可以直接调用,如下所示:

new Test().TestMethod();

如果要调用Test1接口的TestMethod()方法,我们就需要通过接口调用了,如下所示:

((ITest1)new Test()).TestMethod();

这里需要说明的是,显式接口实现方法并不是类型的对象模型的一部分,而只是将方法与接口对应起来,同时避免公开行为.《CLR via C#》(P266)

       另一种重名问题是接口继承接口.

如下所示, ITtest2与ITtest1都有TestMethod()方法签名,同时ITtest2继承自ITtest1,那么ITtest2中只能存在一个方法,会默认隐藏掉ITtest1的方法,这个时候,我们可以使用new关键字.

interface ITtest2: ITest1
{
new void TestMethod();
}

      4:接口继承的问题3—派生问题

当没有重名问题的时候,C#编译器要求将接口方法标记为public,同时CLR会将方法默认标记为virtual和sealed,这样可以公开调用但是不能被派生类重写.如下所示:

class Base : ITest1
{
public void TestMethod ()
{
}
}
class Test : Base
{
}

也就是说Test类不能重写TestMethod方法.但是,如果我们把TestMethod加上virtual,那么CLR会默认去掉sealed标记,这样Test类就可以重写TestMethod方法了.如下所示

class Base : ITest1
{
public virtual void TestMethod()
{
}
}
class Test : Base
{
public override void TestMethod()
{
Console.WriteLine("Hello");
}
}

      当使用显式接口实现(重名和不重名的时候都可以用)的时候,这样的方法不能加virtural关键字,所以这个时候它不能被Test类所重写.如下所示:

class Base : ITest1
{
void ITest1.TestMethod()
{
}
}
class Test : Base
{
//这里不能overvide TestMethod方法
}

四. 接口的调用

接口的调用有两种方式,其实文章的前面已经写出来了,这里再总结一下:

类调用:使用实现这个接口的类来调用接口的方法,它能调用类所有的方法,如下所示:

interface ITest1
{
void TestMethod();
}
class Test : ITest1
{
public void TestSelf()
{
}
public void TestMethod()
{
}
}

接口调用:使用接口来调用接口的方法,它只能调用接口的方法

需要说明的是,类继承的一个重要特点是:凡能使用基类实例的地方,都能使用派生类的实例.

与此相似,接口继承的一个重点特点是:凡能使用接口实例的地方,都能使用实现了接口的一个类型的实例.前面的接口调用依据的就是这样的原理.摘自《CLR via C#》

五. 范型接口

这里直接给出《CLR via C#》中总结的3点.

      1.提供编译时类型安全     

当我们使用如下代码时,编译是没有问题的:

 int i = ;
int j= i.CompareTo("");

但在调用的时候,因为字符串与整形不能直接比较,因而会报错:

这个时候我们就一脸懵逼了,你要是早点告诉我就好了,ok我们使用范型版本:

       2.减少装箱的次数       

 int i = ;
int m=;
int j= i.CompareTo(m);

别看这里比较的都是值类型,但是这里m装箱了,因为CompareTo期待的是object类型.使用范型可以有效地规避这一问题,因为它会直接让你传入相应的类型数据.

      3.可以实现同一个接口若干次

class Test : IComparable<string>, IComparable<int>
{
public int CompareTo(int other)
{
throw new NotImplementedException();
}
public int CompareTo(string other)
{
throw new NotImplementedException();
}
}

六. 范型接口的约束

使用范型接口时,我们可以限定,参数T必须实现了哪几个类和接口,才可以作为参数.以便更精细地控制我们的类.

如下所示:

为什么传入Guid类型的不行呢?

因为我们要求参数,同时实现了IComparable, IConvertible两个接口(where T: IComparable, IConvertible),但Guid类只实现了一个.

同时,C#编译器会为接口约束生成特殊的IL指令,以减少装箱操作,这里向M传入x参数不会造成装箱操作.如下所示:

需要说明的是,下面的例子,在实例化t时不会发生装箱,在调用CompareTo方法传递参数3的时候会发生装箱,但此时实例t仍然不会发生装箱,用Jeffrey Richter的话就是:如果值类型实现了一个接口方法,在值类型的实例上调用接口方法不会造成值类型的实例装箱.

      

七. 使用非范型接口的问题

前面我们探讨过使用接口遇到的几个问题,比如,一旦继承了一个接口,即使子类不需要全部的方法,也需要一一实现接口方法.这些是使用接口(范型接口和非范型接口)共同的问题,.NetFramework在非范型接口之后又提供了范型接口,肯定是范型接口解决了非范型接口的某些问题,为我们编程提供了更好的编程体验和性能.

举一例,在使用IComparable接口时,它的Compare方法接收一个object参数,使用这个版本的非范型版本最大的问题是会遇到编译时安全性问题.如下所示

从上面的代码,我们可以看出,在转换的时候,因为不能控制传入的类型,所以,在类型转换时会出错.

我们可以利用范型接口来解决这个问题,使用显式实现接口,使用类实例的时候,这个接口就可以不被外界访问了,同时用一个同名方法来实现我们的方法.

这里我们在编译时就可以查出这个问题,增强了编译时的安全性.如下所示:

但有一个问题,如果我们使用接口实例来访问接口方法,那么前面所述的问题同样会出现.

八. 使用显式实现接口的问题

在第7点,我们利用了显式实现接口时,接口方法就是private了.但这个特性有时也会带来新的问题,如下所示:

在派生类中无法访问基类的接口方法.对于这样的问题,与第7点中的例子相似,我们提供了一个同名方法:

 public class Base : IComparable
{
public int CompareTo(object obj)
{
return ;
}
int IComparable.CompareTo(object obj)
{
return ;
}
}
public class Drived : Base
{
public void Test()
{
base.CompareTo();
}
}

这样就可以避免无法访问的问题.

九. 接口与基类的设计原则

既然接口与基类都在继承方面表现出了自己的特色.那有一个问题摆在我们面前:什么时候选择接口,什么时候选择基类?

要回答这个问题,我们要知道两者在使用时的一些特点:

接口:只是定义规范,每个继承接口的类都要实现一套自己的方法,彼此不能共享代码.如果接口有修正的时候,所有的子类都需要进行相应的变更.

基类:多个子类可以共享代码,对基类进行方法的添加时,子类可以直接用,而不需要进行相应的修改.

至于怎么用,因人因地而异.不过,我比较赞同Jeffrey Richter的建议:当父类与子类的关系是IS-A的关系,可以使用基类,如哺乳动物;当父类与子类的关系是CAN-DO的关系时,可以使用接口,如耕田

十. 总结

1.接口只定义方法签名与实现

2.必须显式实现接口的方法

3.可以显式接口方法实现,但会改变方法的访问级别

4.C#编译器为范型接口提供了约束,并进行了优化

十一. 参考文档

《CLR via C#(第4版)》

C#夯实基础之接口(《CLR via C#》读书笔记)的更多相关文章

  1. CLR via C# 读书笔记-26.线程基础

    前言 这俩个月没怎么写文章做记录分享,一直在忙项目上线的事情,但是学习这件事情,停下来就感觉难受,clr线程这章也是反复看了好多遍,书读百遍其义自见,今天我们来聊下线程基础 1.进程是什么,以及线程起 ...

  2. [Clr via C#读书笔记]Cp6类型和成员基础

    Cp6类型和成员基础 成员 常量:字段(静态字段和实例字段):实例构造器:类型构造器(用于静态字段的构造):方法(静态方法和实例方法):操作符重载(本质是一个方法):转换操作符:属性(本质还是方法): ...

  3. Clr Via C#读书笔记---线程基础

    趣闻:我是一个线程:http://kb.cnblogs.com/page/542462/ 进程与线程 进程:应用程序的一个实例使用的资源的集合.每个进程都被赋予了一个虚拟地址空间. 线程:对CPU进行 ...

  4. CLR via c#读书笔记九:接口

    1.接口对一组方法签名进行了统一命名.接口还能定义事件.无参属性和有参属性(C#的索引器). 2.c#禁止接口定义任何一种静态成员. 3.C#编译器要求将实现接口的方法标记为public.CLR要求将 ...

  5. CLR via C#读书笔记二:类型基础

    1.CLR允许将对象转换为它的(实际)类型或者它的任何基类型. 2.is操作符检测对象是否兼容于指定类型,is操作符永远不抛出异常. 3.as操作符返回对同一个对象的非null引用.如果对象不兼容,a ...

  6. [Clr via C#读书笔记]Cp13接口

    Cp13接口 类和接口继承 接口只提供签名,不提供实现:等效于契约:凡事能使用具名接口的地方都能够使用实现了的接口. 定义接口 定义很简单,FCL也提供了大量的现成接口供使用: 继承接口 类不能多继承 ...

  7. [Clr via C#读书笔记]Cp4类型基础

    Cp4类型基础 Object类型 Object是所有类型的基类,有Equals,GetHashCode,ToString,GetType四个公共方法,其中GetHashCode,ToString可以o ...

  8. Clr Via C#读书笔记---计算限制的异步操作

    线程池基础 1,线程的创建和销毁是一个昂贵的操作,线程调度以及上下文切换耗费时间和内存资源. 2,线程池是一个线程集合,供应你的用程序使用. 3,每个CLR有一个自己的线程池,线程池由CLR控制的所有 ...

  9. CLR via C# 读书笔记-21.托管堆和垃圾回收

    前言 近段时间工作需要用到了这块知识,遂加急补了一下基础,CLR中这一章节反复看了好多遍,得知一二,便记录下来,给自己做一个学习记录,也希望不对地方能够得到补充指点. 1,.托管代码和非托管代码的区别 ...

随机推荐

  1. Style样式

    最重要的两个元素 :setter  Trigger  Style中的Setter setter是用来设置属性值的 <Style TargetType="{x:Type TextBox} ...

  2. 【C#】类单例 可以解决全局变量的问题

    单件模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点. 知道 详解

  3. HTML <meta> 标签,搜索引擎

    关于Mate标签的详尽解释,请查看w3school 网址为:http://www.w3school.com.cn/tags/tag_meta.asp meta标签作用 META标签是HTML标记HEA ...

  4. js或css文件后面的参数是什么意思?

    经常看到不少导航网站测样式或js文件后面加了一些参数,主要是一你为一些并不经常更新的页面重新加载新修改的文件. 经常遇到页面里加载的js与css文件带有参数,比如: <script type=& ...

  5. xamarin.forms uwp app部署到手机移动设备进行测试,真机调试(device portal方式部署)

    最近学习xamarin.刚好 手上有一个lumia 930.所以试一试把uwp app部署到手机上,并真机调试一把. 目前环境: 1.开发pc电脑是win10,版本1607.加入了insider,所以 ...

  6. neo4j关闭和开启密码访问权限

    本例:neo4j-enterprise-2.3.1版本 neo4j默认安装是开启访问密码验证 可以发现,在conf/下的neo4j-server.properties配置文件 # Require (o ...

  7. vue-Resource(与后端数据交互)

    单来说,vue-resource就像jQuery里的$.ajax,用来和后端交互数据的.可以放在created或者ready里面运行来获取或者更新数据... vue-resource文档:https: ...

  8. CentOS 6/7安装ffmpeg

    环境 CentOS 6/7 安装 导入GPG key rpm --import http://packages.atrpms.net/RPM-GPG-KEY.atrpms 安装ATRPMS Repo ...

  9. x509数字证书导入-然后删除自身

    这种程序的使用场景,需要给客户一个证书,但不能把证书直接给他让他安装,程序中需要用到给客户的私钥,但又不允许客户将这个证书再去授权给其它人. 重点并不是代码,是如何对用户隐藏需要添加的资源 ,以文本为 ...

  10. HTML5学习总结-番外03 Angular Ionic

    一 Angular Angular使用了MVVC设计模式,MVVC在概念上是真正将页面与数据逻辑分离的模式,它把数据绑定工作放到一个JS里去实现,而这个JS文件的主要功能是完成数据的绑定,即把mode ...