本筆記摘抄自:https://www.cnblogs.com/solan/archive/2012/08/01/CSharp06.html,記錄一下學(xué)習(xí)過程以備后續(xù)查用。 摘要: 抽象類:是一種特殊的類,可以定義具有實(shí)現(xiàn)的方法,也可以定義未實(shí)現(xiàn)的方法契約,本身不能被實(shí)例化,只能在派生類中進(jìn)行實(shí)例化。接口:對(duì)一 組方法簽名進(jìn)行統(tǒng)一的命名,只能定義未實(shí)現(xiàn)的方法契約,本身也不能被實(shí)例化,只能在實(shí)現(xiàn)類中進(jìn)行實(shí)例化。 二者都可以有部分?jǐn)?shù)據(jù)成員(如:屬性),它們貌似有著相同的“契約”功能,但對(duì)各自的派生類(實(shí)現(xiàn)類)又有著不同的要求,那么,到底它們有何 異同呢?下面將從四個(gè)方面來講解它們的相同與不同之處。 一、定義 抽象類 不能實(shí)例化。抽象類的用途是提供多個(gè)派生類可共享的基類的公共定義,是對(duì)類進(jìn)行抽象,可以有實(shí)現(xiàn),也可以不實(shí)現(xiàn)。使用關(guān)鍵字abstract 進(jìn)行定義。 下面定義一個(gè)抽象類: 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 可以看以,抽象類實(shí)際上是繼承了System.Object類,并且編譯器為它生成了一個(gè)默認(rèn)的構(gòu)造函數(shù)。 接口 它是對(duì)一組方法簽名進(jìn)行統(tǒng)一命名,是對(duì)一組行為規(guī)范的定義,使用關(guān)鍵字interface進(jìn)行定義。 下面定義一個(gè)接口: public interface ICode_06_01 { } 通過ISDASM來看一下生成的IL: .class interface abstract auto ansi nested public ICode_06_01 { } // end of class ICode_06_01 可以看到,接口實(shí)際上是把它當(dāng)成抽象類來看待,但是沒有構(gòu)造函數(shù)。無論是抽象類擁有構(gòu)造函數(shù),還是接口不擁有構(gòu)造函數(shù),它們都是不能被實(shí)例 化的。 二、成員的區(qū)別 抽象類 描述: 1)可以定義抽象方法,抽象方法沒有具體實(shí)現(xiàn),僅僅是一個(gè)方法的契約,在子類中重寫該方法。抽象類可以重寫父類的虛方法為抽象方法。 2)可以定義非抽象方法,但要求該方法要有具體實(shí)現(xiàn),如果該方法是虛方法,則在子類中可以重寫該方法。 3)可以定義字段、屬性、抽象屬性、事件及靜態(tài)成員。 下面是對(duì)類Code_06_03的擴(kuò)充: ![]() 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方法當(dāng)作一個(gè)虛方法,在子類中可以被重寫。 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 它本來就是一個(gè)虛方法,所以編譯器并沒有特殊對(duì)待它。 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 它也是一個(gè)普通的對(duì)象方法。 接口 描述: 1)可以定義屬性及索引器,但不能定義字段。 2)可以定義事件。 3)可以定義方法,僅僅是方法簽名的約定,不得有實(shí)現(xiàn),在實(shí)現(xiàn)類中對(duì)該方法進(jìn)行具體實(shí)現(xiàn),有點(diǎn)類似于抽象類的抽象方法。 4)不可以定義虛方法。 5)不可以定義任何靜態(tài)成員。 6)接口成員默認(rèn)是全開放的,不得有訪問修飾符。 下面是對(duì)類Code_06_01的擴(kuò)充: ![]() 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 可以看到,定義的時(shí)候,我們并沒有為其指定可訪問修飾符(編譯器也不允許我們明文指定其可訪問修飾符),但編譯器默認(rèn)將它的訪問級(jí) 別指定為public,另外是把它當(dāng)作一個(gè)抽象的虛方法。 至于成員屬性和事件,編譯器則將它們當(dāng)作普通的對(duì)象屬性和對(duì)象事件對(duì)待,會(huì)為它們生成相應(yīng)的get/set和add/remove 方法,并無特別之 處。 三、實(shí)現(xiàn)方式的區(qū)別 抽象類 實(shí)現(xiàn): 由于抽象類也是類,所以對(duì)它的實(shí)現(xiàn)就像普通的繼承一樣,子類通過繼承可以得到抽象類的公有成員,且可以重寫部分成員,如虛方法和抽象 方法等。 下面是對(duì)Code_06_03類的實(shí)現(xiàn): ![]() 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> /// 抽象類的實(shí)現(xiàn) /// </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是標(biāo)準(zhǔn)地對(duì)繼承類Code_06_03,兩個(gè)重寫的方法Add和GetAddress都是普通的對(duì)象方法,只是依然被 當(dāng)作虛方法來看待。 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 因?yàn)檫@兩個(gè)方法保持著虛方法的特性,所以對(duì)于Code_06_04類的子類,同樣還可以重寫這兩個(gè)方法。屬性成員Address這里還 是一普通的對(duì)象屬性。 接口 實(shí)現(xiàn) 對(duì)接口的實(shí)現(xiàn)跟對(duì)抽象類的實(shí)現(xiàn)相似,下面是對(duì)接口ICode_06_01的實(shí)現(xiàn): ![]() class Program { /// <summary> /// 接口 /// </summary> public interface ICode_06_01 { string Name { get; set; } int Add(int a, int b); event EventHandler AddEvent; } /// <summary> /// 接口的實(shí)現(xiàn) /// </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: 它與普通類的區(qū)別不大,只是很明確的是實(shí)現(xiàn)了接口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類,同時(shí)還實(shí)現(xiàn)了接口ICode_06_01。再來看一下對(duì)于接口中的方法,編 譯器是如何處理的? 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 編譯器認(rèn)為Add方法具有虛方法的特性。而對(duì)于屬性和事件,依然是普通的實(shí)現(xiàn),如get/set、add/remove。另外,接口還支持 顯示實(shí)現(xiàn)接口,我們上面討論的Code_06_02類對(duì)接口的實(shí)現(xiàn)默認(rèn)是隱式實(shí)現(xiàn)。 在接口的實(shí)現(xiàn)類內(nèi)部,可以存在一個(gè)與接口某一方法名(包括簽名)完全相同的方法,但要求對(duì)接口實(shí)現(xiàn)的那個(gè)方法必須是顯 示實(shí)現(xiàn),如下代碼: public int Add(int a, int b) { return a + b; } int ICode_06_01.Add(int a, int b) { OnAdded(); return a + b; } 可以看出顯示實(shí)現(xiàn)就是在方法前加上接口名和點(diǎn)號(hào)(ICode_06_01.),另外方法是不能有可訪問修飾符的,編譯器會(huì)對(duì)其進(jìn)行 private處理。那如何才能調(diào)用顯示實(shí)現(xiàn)的接口方法呢?可以將實(shí)現(xiàn)類的對(duì)象轉(zhuǎn)為一個(gè)接口變量,再調(diào)用該變量的相應(yīng)方法,如下 代碼: 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(); } 而對(duì)于抽象類的實(shí)現(xiàn),是不能進(jìn)行顯示實(shí)現(xiàn)的。 四、應(yīng)用中的區(qū)別 1)抽象類保留一普通類的部分特性,定義可能已經(jīng)實(shí)現(xiàn)的方法行為,方法內(nèi)可以對(duì)數(shù)據(jù)成員(如屬性)進(jìn)行操作,且方法可以 相互溝通。而接口僅僅是定義方法的簽名,就像規(guī)則,只是約定,并沒有實(shí)現(xiàn)。 2)抽象類的派生類可以原封不動(dòng)地得到抽象類的部分成員,接口的實(shí)現(xiàn)類如果想要得到接口的數(shù)據(jù)成員,則必須對(duì)其進(jìn)行重寫。 3)一個(gè)類只能繼承于一個(gè)類(含抽象類),但可以實(shí)現(xiàn)多個(gè)接口,并且可以在繼承一個(gè)基類的基礎(chǔ)上,同時(shí)實(shí)現(xiàn)多個(gè)接口。 4)抽象類和接口都不能對(duì)其使用密封sealed,事實(shí)上這兩者都是為了被其他類繼承和實(shí)現(xiàn),對(duì)其使用sealed是沒有任何意義的。 5)抽象類可以對(duì)接口進(jìn)行實(shí)現(xiàn)。 6)抽象類更多的用于“復(fù)制對(duì)象副本”,就是我們常說的“子類與父類有著is a的關(guān)系”,它更多關(guān)注于一個(gè)對(duì)象的整體特性。接口 更多傾向于一系列的方法操作,這些操作在當(dāng)前上下文中既有著相同作用對(duì)象,又相互隔離。 7)某些時(shí)候,抽象類可以與接口互換。 通過生活中常見的紅娘搭線的示例:紅娘(Matchmaker)安排相親者(wooer)見面并指導(dǎo)場(chǎng)面話,來說明接口與抽象類給我們 帶來的方便性。 下面代碼演示不使用接口與抽象類的紅娘搭線: ![]() class Program { /// <summary> /// 紅娘類 /// </summary> public class Matchmaker { string message; /// <summary> /// 場(chǎng)面話、客套話指導(dǎo) /// </summary> public void Teach() { message = "曾經(jīng)有一份真摯的愛情擺在我面前……"; Wooer wooer = new Wooer(); wooer.Say(message); } } /// <summary> /// 相親者類 /// </summary> public class Wooer { /// <summary> /// 場(chǎng)面話、客套話大全 /// </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 } } 運(yùn)行結(jié)果如下: 以上功能實(shí)現(xiàn)沒有問題,但是假如相親者想要增加一點(diǎn)肢體動(dòng)作或文藝展示來博取對(duì)方好感的話,紅娘就得跟著變。于是,紅娘 搭建了一個(gè)相親平臺(tái)…… 下面代碼演示使用接口與抽象類的紅娘搭線: ![]() class Program { /// <summary> /// 紅娘類 /// </summary> public class MatchmakerNew { string message; /// <summary> /// 場(chǎng)面話、客套話指導(dǎo) /// </summary> public void Teach(IWooer wooer) { message = "曾經(jīng)有一份真摯的愛情擺在我面前……"; 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> /// 男相親者實(shí)現(xiàn)類 /// </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> /// 女相親者實(shí)現(xiàn)類 /// </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 } } 運(yùn)行結(jié)果如下: |
|