本笔记摘抄自: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(, );
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
}
}

运行结果如下:

C#接口与抽象类学习笔记的更多相关文章

  1. C#中的接口和抽象类学习

    今天学习了接口和抽象类,但并没有很好的进行整理,所以现在写的时候,脑子里多少有点乱乱的,先从接口开始吧. interface 接口,规定了所有派生类的需要遵循的标准,接口定义了需要做些什么,但是没有具 ...

  2. 接口与协议学习笔记-USB协议_USB2.0_USB3.0不同版本(三)

    USB(Universal Serial Bus)全称通用串口总线,USB为解决即插即用需求而诞生,支持热插拔.USB协议版本有USB1.0.USB1.1.USB2.0.USB3.1等,USB2.0目 ...

  3. 接口与协议学习笔记-AMBA片上通信协议_APB_AHB_AXI_AXI4不同版本(二)

    随着深亚微米工艺技术日益成熟,集成电路芯片的规模越来越大.数字IC从基于时序驱动的设计方法,发展到基于IP复用的设计方法,并在SOC设计中得到了广泛应用.在基于IP复用的SoC设计中,片上总线设计是最 ...

  4. Java 多态 接口继承等学习笔记

    Super关键字 1.子类可以调用父类声明的构造方法 : 语法:在子类的构造方法中使用super关键字  super(参数列表) 2.操作被隐藏的成员变量(子类的成员变量和父类的成员变量重名的说法)和 ...

  5. 80x86/Pentium微机原理及接口技术-微处理器-学习笔记

    80x86/  Pentium微机原理及接口技术 1.    计算机基础... 1 1.1常用术语... 1 1.2计算机中数与编码的表示方法... 1 1.2.1进制表示及进制转换... 1 1.2 ...

  6. 接口与协议学习笔记-Ethernet UDP通信协议(一)

    总线(BUS,即公共汽车,数据的公共传输路线)分类的方式有很多,如被分为外部和内部总线.系统总线和非系统总线等等,下面是几种最常用的分类方法.另外,总线的传输核心思想是多路复用:时分多路复用-TDMA ...

  7. Java基础学习笔记总结

    Java基础学习笔记一 Java介绍 Java基础学习笔记二 Java基础语法之变量.数据类型 Java基础学习笔记三 Java基础语法之流程控制语句.循环 Java基础学习笔记四 Java基础语法之 ...

  8. Java8学习笔记目录

    Java8学习笔记(一)--Lambda表达式 Java8学习笔记(二)--三个预定义函数接口 Java8学习笔记(三)--方法引入 Java8学习笔记(四)--接口增强 Java8学习笔记(五)-- ...

  9. [Golang学习笔记] 02 命令源码文件

    源码文件的三种类型: 命令源文件:可以直接运行的程序,可以不编译而使用命令“go run”启动.执行. 库源码文件 测试源码文件 面试题:命令源码文件的用途是什么,怎样编写它? 典型回答: 命令源码文 ...

随机推荐

  1. docker介绍 架构 安装

    Docker是什么? docker是一个开源的软件部署解决方案: docker也是轻量级的应用容器框架: docker可以打包.发布.运行任何的应用. Docker 是一个开源的应用容器引擎,基于 G ...

  2. Mysql 初始化 及 密码管理

    安装好mysql后,第一次初始化数据库 前言:启动mysql数据库最好不要使用root用户,而是使用mysql用户启动 官方解释: (永远不要使用root帐号启动MySQL Server.这样做很危险 ...

  3. 分区格式化大于2 TiB磁盘

    如果您要分区格式化一块大于2 TiB的作数据盘用的云盘(本文统一称为 大容量数据盘,小于2 TiB的数据盘统称为 小容量数据盘),您必须采用GPT分区形式.本文档描述了如何在不同的操作系统里分区格式化 ...

  4. expect知识梳理

    1 expect expect软件用于实现非交互式操作,实际应用中常用于批量部署,可以帮助运维人员管理成千上万台服务器. expect实现非交互式操作主要是在程序发出交互式询问时,按条件传递程序所需的 ...

  5. C++泛化动态数组

    泛化动态数组 动态数组的核心思想是在存储数据时动态的管理数组元素占用的内存,通过调用动态数组的类方法来对数组中的数据进行增删改查操作.最初我们为数组申请10个元素的空间,放我们不断向数组中添加数据时, ...

  6. UML之二、建模元素(1)

    本章介绍UML建模元素 1:Stereotype-也被称为类型.构造型 UML里的元素扩展,简单来说其功能就是在已有的类型上添加一些标记,类似于打个戳,从而生成新的东西. 简单的说加一句话来更加清楚准 ...

  7. StarUML之六、StarUML规则与快捷键

    本章内容参考官网即可,不做详细说明,实践出真知! starUMl规则主要是在模型设计的约束条件 https://docs.staruml.io/user-guide/validation-rules ...

  8. GitLab Container Registry

    通过将GitLab Container Registry集成到GitLab中,每个项目都可以拥有自己的空间来存储其Docker镜像. 1. 启用Container Registry 如果在你的项目的侧 ...

  9. 使用UCSC Genome Browser下载人类所有mRNA序列

    打开UCSC Genome Browser官网.网址:http://genome.ucsc.edu/ 点击导航栏的Genome Data 在新的页面中,点击human,可快速定位至页面中人类基因组数据 ...

  10. 【56】目标检测之NMS非极大值抑制

    非极大值抑制(Non-max suppression) 到目前为止你们学到的对象检测中的一个问题是,你的算法可能对同一个对象做出多次检测,所以算法不是对某个对象检测出一次,而是检测出多次.非极大值抑制 ...