C#接口与抽象类学习笔记
本笔记摘抄自:https://www.cnblogs.com/solan/archive/2012/08/01/csharp06.html,记录一下学习过程以备后续查用。
摘要:
抽象类:是一种特殊的类,可以定义具有实现的方法,也可以定义未实现的方法契约,本身不能被实例化,只能在派生类中进行实例化。接口:对一
组方法签名进行统一的命名,只能定义未实现的方法契约,本身也不能被实例化,只能在实现类中进行实例化。
二者都可以有部分数据成员(如:属性),它们貌似有着相同的“契约”功能,但对各自的派生类(实现类)又有着不同的要求,那么,到底它们有何
异同呢?下面将从四个方面来讲解它们的相同与不同之处。
一、定义
抽象类 不能实例化。抽象类的用途是提供多个派生类可共享的基类的公共定义,是对类进行抽象,可以有实现,也可以不实现。使用关键字abstract
进行定义。
下面定义一个抽象类:
public abstract class code_06_03 { }
通过isdasm来看一下生成的il:
.class abstract auto ansi nested public beforefieldinit code_06_03 extends [mscorlib]system.object { } // end of class code_06_03
可以看以,抽象类实际上是继承了system.object类,并且编译器为它生成了一个默认的构造函数。
接口 它是对一组方法签名进行统一命名,是对一组行为规范的定义,使用关键字interface进行定义。
下面定义一个接口:
public interface icode_06_01 { }
通过isdasm来看一下生成的il:
.class interface abstract auto ansi nested public icode_06_01 { } // end of class icode_06_01
可以看到,接口实际上是把它当成抽象类来看待,但是没有构造函数。无论是抽象类拥有构造函数,还是接口不拥有构造函数,它们都是不能被实例
化的。
二、成员的区别
抽象类 描述:
1)可以定义抽象方法,抽象方法没有具体实现,仅仅是一个方法的契约,在子类中重写该方法。抽象类可以重写父类的虚方法为抽象方法。
2)可以定义非抽象方法,但要求该方法要有具体实现,如果该方法是虚方法,则在子类中可以重写该方法。
3)可以定义字段、属性、抽象属性、事件及静态成员。
下面是对类code_06_03的扩充:
class program { /// <summary> /// 抽象类 /// </summary> public abstract class code_06_03 { dictionary<guid, string> root = new dictionary<guid, string>(); public string sex { get; set; } public abstract string address { get; } public abstract int add(int a, int b); protected virtual string getaddress(string addressid) { return addressid + " 广东"; } public void addroot(guid id, string rootname) { root.add(id, rootname); onaddroot(); } public event eventhandler addrootevent; void onaddroot() { addrootevent?.invoke(this, null); } public string this[guid key] { get { return root[key]; } set { root[key] = value; } } } static void main(string[] args) { } }
2.1抽象方法public abstract int add(int a, int b);的il:
.method public hidebysig newslot abstract virtual instance int32 add(int32 a, int32 b) cil managed { } // end of method code_06_03::add
编译器把add方法当作一个虚方法,在子类中可以被重写。
2.2虚方法protected virtual string getaddress(string addressid)的il:
.method family hidebysig newslot virtual instance string getaddress(string addressid) cil managed { // 略过 } // end of method code_06_03::getaddress
它本来就是一个虚方法,所以编译器并没有特殊对待它。
2.3方法public void addroot(guid id, string rootname)的il:
.method public hidebysig instance void addroot(valuetype [mscorlib]system.guid id, string rootname) cil managed { // 略过 } // end of method code_06_03::addroot
它也是一个普通的对象方法。
接口 描述:
1)可以定义属性及索引器,但不能定义字段。
2)可以定义事件。
3)可以定义方法,仅仅是方法签名的约定,不得有实现,在实现类中对该方法进行具体实现,有点类似于抽象类的抽象方法。
4)不可以定义虚方法。
5)不可以定义任何静态成员。
6)接口成员默认是全开放的,不得有访问修饰符。
下面是对类code_06_01的扩充:
class program { /// <summary> /// 接口 /// </summary> public interface icode_06_01 { string name { get; set; } int add(int a, int b); event eventhandler addevent; } static void main(string[] args) { } }
2.4方法int add(int a, int b);的il:
.method public hidebysig newslot abstract virtual instance int32 add(int32 a, int32 b) cil managed { } // end of method icode_06_01::add
可以看到,定义的时候,我们并没有为其指定可访问修饰符(编译器也不允许我们明文指定其可访问修饰符),但编译器默认将它的访问级
别指定为public,另外是把它当作一个抽象的虚方法。
至于成员属性和事件,编译器则将它们当作普通的对象属性和对象事件对待,会为它们生成相应的get/set和add/remove 方法,并无特别之
处。
三、实现方式的区别
抽象类 实现:
由于抽象类也是类,所以对它的实现就像普通的继承一样,子类通过继承可以得到抽象类的公有成员,且可以重写部分成员,如虚方法和抽象
方法等。
下面是对code_06_03类的实现:
class program { /// <summary> /// 抽象类 /// </summary> public abstract class code_06_03 { dictionary<guid, string> root = new dictionary<guid, string>(); public string sex { get; set; } public abstract string address { get; } /// <summary> /// 抽象方法add /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns></returns> public abstract int add(int a, int b); /// <summary> /// 虚方法getaddress /// </summary> /// <param name="addressid"></param> /// <returns></returns> protected virtual string getaddress(string addressid) { return addressid + " 广东"; } public void addroot(guid id, string rootname) { root.add(id, rootname); onaddroot(); } public event eventhandler addrootevent; void onaddroot() { addrootevent?.invoke(this, null); } public string this[guid key] { get { return root[key]; } set { root[key] = value; } } } /// <summary> /// 抽象类的实现 /// </summary> public class code_06_04 : code_06_03 { public override int add(int a, int b) { return a + b; } protected override string getaddress(string addressid) { return "guangdong"; } readonly string addressprefix = "china "; public override string address { get { return addressprefix; } } } static void main(string[] args) { } }
通过isdasm来看一下生成的il:
可以看到类code_06_04是标准地对继承类code_06_03,两个重写的方法add和getaddress都是普通的对象方法,只是依然被
当作虚方法来看待。
3.1方法add的il:
.method public hidebysig virtual instance int32 add(int32 a, int32 b) cil managed { // 略过 } // end of method code_06_04::add
3.2方法getaddress的il:
.method family hidebysig virtual instance string getaddress(string addressid) cil managed { // 略过 } // end of method code_06_04::getaddress
因为这两个方法保持着虚方法的特性,所以对于code_06_04类的子类,同样还可以重写这两个方法。属性成员address这里还
是一普通的对象属性。
接口 实现
对接口的实现跟对抽象类的实现相似,下面是对接口icode_06_01的实现:
class program { /// <summary> /// 接口 /// </summary> public interface icode_06_01 { string name { get; set; } int add(int a, int b); event eventhandler addevent; } /// <summary> /// 接口的实现 /// </summary> public class code_06_02 : icode_06_01 { public string name { get; set; } public int add(int a, int b) { onadded(); return a + b; } public event eventhandler addevent; void onadded() { addevent?.invoke(this, null); } } static void main(string[] args) { } }
通过isdasm来看一下生成的il:
它与普通类的区别不大,只是很明确的是实现了接口icode_06_01,来看一下它的il:
.class auto ansi nested public beforefieldinit code_06_02 extends [mscorlib]system.object implements linkto.test.interfaceandabstractclass.program/icode_06_01 { } // end of class code_06_02
可以看到,类code_06_02不仅继承于system.object类,同时还实现了接口icode_06_01。再来看一下对于接口中的方法,编
译器是如何处理的?
3.3方法add的il:
.method public hidebysig newslot virtual final instance int32 add(int32 a, int32 b) cil managed { // 略过 } // end of method code_06_02::add
编译器认为add方法具有虚方法的特性。而对于属性和事件,依然是普通的实现,如get/set、add/remove。另外,接口还支持
显示实现接口,我们上面讨论的code_06_02类对接口的实现默认是隐式实现。
在接口的实现类内部,可以存在一个与接口某一方法名(包括签名)完全相同的方法,但要求对接口实现的那个方法必须是显
示实现,如下代码:
public int add(int a, int b) { return a + b; } int icode_06_01.add(int a, int b) { onadded(); return a + b; }
可以看出显示实现就是在方法前加上接口名和点号(icode_06_01.),另外方法是不能有可访问修饰符的,编译器会对其进行
private处理。那如何才能调用显示实现的接口方法呢?可以将实现类的对象转为一个接口变量,再调用该变量的相应方法,如下
代码:
static void main(string[] args) { code_06_02 code0602 = new code_06_02(); icode_06_01 icode0602 = code0602; var result = icode0602.add(1, 2); console.writeline($"result={result}"); console.read(); }
而对于抽象类的实现,是不能进行显示实现的。
四、应用中的区别
1)抽象类保留一普通类的部分特性,定义可能已经实现的方法行为,方法内可以对数据成员(如属性)进行操作,且方法可以
相互沟通。而接口仅仅是定义方法的签名,就像规则,只是约定,并没有实现。
2)抽象类的派生类可以原封不动地得到抽象类的部分成员,接口的实现类如果想要得到接口的数据成员,则必须对其进行重写。
3)一个类只能继承于一个类(含抽象类),但可以实现多个接口,并且可以在继承一个基类的基础上,同时实现多个接口。
4)抽象类和接口都不能对其使用密封sealed,事实上这两者都是为了被其他类继承和实现,对其使用sealed是没有任何意义的。
5)抽象类可以对接口进行实现。
6)抽象类更多的用于“复制对象副本”,就是我们常说的“子类与父类有着is a的关系”,它更多关注于一个对象的整体特性。接口
更多倾向于一系列的方法操作,这些操作在当前上下文中既有着相同作用对象,又相互隔离。
7)某些时候,抽象类可以与接口互换。
通过生活中常见的红娘搭线的示例:红娘(matchmaker)安排相亲者(wooer)见面并指导场面话,来说明接口与抽象类给我们
带来的方便性。
下面代码演示不使用接口与抽象类的红娘搭线:
class program { /// <summary> /// 红娘类 /// </summary> public class matchmaker { string message; /// <summary> /// 场面话、客套话指导 /// </summary> public void teach() { message = "曾经有一份真挚的爱情摆在我面前……"; wooer wooer = new wooer(); wooer.say(message); } } /// <summary> /// 相亲者类 /// </summary> public class wooer { /// <summary> /// 场面话、客套话大全 /// </summary> /// <param name="message"></param> public void say(string message) { console.writeline(message); } } static void main(string[] args) { #region 不使用接口及抽象类的红娘搭线 matchmaker matchmaker = new matchmaker(); matchmaker.teach(); console.read(); #endregion } }
运行结果如下:
以上功能实现没有问题,但是假如相亲者想要增加一点肢体动作或文艺展示来博取对方好感的话,红娘就得跟着变。于是,红娘
搭建了一个相亲平台……
下面代码演示使用接口与抽象类的红娘搭线:
class program { /// <summary> /// 红娘类 /// </summary> public class matchmakernew { string message; /// <summary> /// 场面话、客套话指导 /// </summary> public void teach(iwooer wooer) { message = "曾经有一份真挚的爱情摆在我面前……"; wooer.say(message); } } /// <summary> /// 相亲者接口 /// </summary> public interface iwooer { /// <summary> /// 房子车子票子…… /// </summary> string message { get; } /// <summary> /// 能歌善舞…… /// </summary> void action(); /// <summary> /// 甜言蜜语…… /// </summary> /// <param name="message"></param> void say(string message); } /// <summary> /// 男相亲者实现类 /// </summary> public class manwooer : iwooer { public string message { get { return "嫁给我,房子车子票子啥都有。"; } } public void action() { console.writeline("野狼disco……"); } public void say(string message) { action(); console.writeline(message + message); } } /// <summary> /// 女相亲者实现类 /// </summary> public class womanwooer : iwooer { public string message { get { return "娶了我,这头牛和后面的这座山都是你的。"; } } public void action() { console.writeline("相见恨晚……"); } public void say(string message) { action(); console.writeline(message + message); } } static void main(string[] args) { #region 使用接口及抽象类的红娘搭线 matchmakernew matchmakernew = new matchmakernew(); //男大为婚 iwooer manwooer= new manwooer(); matchmakernew.teach(manwooer); manwooer.say("亲:"); console.writeline(); //女大为嫁 iwooer womanwooer = new womanwooer(); matchmakernew.teach(womanwooer); womanwooer.say("亲:"); console.read(); #endregion } }
运行结果如下: