欧美亚洲中文,在线国自产视频,欧洲一区在线观看视频,亚洲综合中文字幕在线观看

      1. <dfn id="rfwes"></dfn>
          <object id="rfwes"></object>
        1. 站長資訊網(wǎng)
          最全最豐富的資訊網(wǎng)站

          C# 中的多態(tài)性

          相信大家都對面向?qū)ο蟮娜齻€特征封裝、繼承、多態(tài)很熟悉,每個人都能說上一兩句,但是大多數(shù)都僅僅是知道這些是什么,不知道 CLR 內(nèi)部是如何實現(xiàn)的,所以本篇文章主要說說多態(tài)性中的一些概念已經(jīng)內(nèi)部實現(xiàn)的機理。

          一、多態(tài)的概念

          首先解釋下什么叫多態(tài):同一操作作用于不同的對象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果,這就是多態(tài)性。換句話說,實際上就是同一個類型的實例調(diào)用”相同”的方法,產(chǎn)生的結(jié)果是不同的。這里的”相同”打上雙引號是因為這里的相同的方法僅僅是看上去相同的方法,實際上它們調(diào)用的方法是不同的。

          說到多態(tài),我們不能免俗的提到下面幾個概念:重載、重寫、虛方法、抽象方法以及隱藏方法。下面就來一一介紹他們的概念。

          1、重載(overload): 在同一個作用域(一般指一個類)的兩個或多個方法函數(shù)名相同,參數(shù)列表不同的方法叫做重載,它們有三個特點(俗稱兩必須一可以):

          • 方法名必須相同
          • 參數(shù)列表必須不相同
          • 返回值類型可以不相同

          例如:

          public void Sleep()  {      Console.WriteLine("Animal睡覺");  }  public int Sleep(int time)  {      Console.WriteLine("Animal{0}點睡覺", time);      return time;  }

          2、重寫(override):子類中為滿足自己的需要來重復(fù)定義某個方法的不同實現(xiàn),需要用 override 關(guān)鍵字,被重寫的方法必須是虛方法,用的是 virtual 關(guān)鍵字。它的特點是(三個相同):

          • 相同的方法名
          • 相同的參數(shù)列表
          • 相同的返回值

          如:父類中的定義:

          public virtual void EatFood()  {      Console.WriteLine("Animal吃東西");  }

          子類中的定義:

          public override void EatFood()  {      Console.WriteLine("Cat吃東西");      //base.EatFood();  }

          小提示:經(jīng)常有童鞋問重載和重寫的區(qū)別,而且網(wǎng)絡(luò)上把這兩個的區(qū)別作為 C# 做??嫉拿嬖囶}之一。實際上這兩個概念完全沒有關(guān)系,僅僅都帶有一個”重”字。他們沒有在一起比較的意義,僅僅分辨它們不同的定義就好了。

          3、虛方法:即為基類中定義的允許在派生類中重寫的方法,使用virtual關(guān)鍵字定義。如:

          public virtual void EatFood()  {      Console.WriteLine("Animal吃東西");  }

          注意:虛方法也可以被直接調(diào)用。如:

          Animal a = new Animal();  a.EatFood();

          執(zhí)行輸出結(jié)果為:

          Animal吃東西

          4、抽象方法:在基類中定義的并且必須在派生類中重寫的方法,使用 abstract 關(guān)鍵字定義。如:

          public abstract class Biology  {      public abstract void Live();  }  public class Animal : Biology  {      public override void Live()      {          Console.WriteLine("Animal重寫的抽象方法");          //throw new NotImplementedException();      }   }

          注意:抽象方法只能在抽象類中定義,如果不在抽象類中定義,則會報出如下錯誤:

          C# 中的多態(tài)性

          虛方法和抽象方法的區(qū)別是:因為抽象類無法實例化,所以抽象方法沒有辦法被調(diào)用,也就是說抽象方法永遠不可能被實現(xiàn)。

          5、隱藏方法:在派生類中定義的和基類中的某個方法同名的方法,使用 new 關(guān)鍵字定義。如在基類 Animal 中有一方法 Sleep():

          public void Sleep()  {      Console.WriteLine("Animal Sleep");  }

          則在派生類 Cat 中定義隱藏方法的代碼為:

          new public void Sleep()  {      Console.WriteLine("Cat Sleep");  }

          或者為:

          public new void Sleep()  {      Console.WriteLine("Cat Sleep");  } 

          注意:

          • (1)隱藏方法不但可以隱藏基類中的虛方法,而且也可以隱藏基類中的非虛方法。
          • (2)隱藏方法中父類的實例調(diào)用父類的方法,子類的實例調(diào)用子類的方法。
          • (3)和上一條對比:重寫方法中子類的變量調(diào)用子類重寫的方法,父類的變量要看這個父類引用的是子類的實例還是本身的實例,如果引用的是父類的實例那么調(diào)用基類的方法,如果引用的是派生類的實例則調(diào)用派生類的方法。

          好了,基本概念講完了,下面來看一個例子,首先我們新建幾個類:

          public abstract class Biology  {      public abstract void Live();  }  public class Animal : Biology  {      public override void Live()      {          Console.WriteLine("Animal重寫的Live");          //throw new NotImplementedException();      }      public void Sleep()      {          Console.WriteLine("Animal Sleep");      }      public int Sleep(int time)      {          Console.WriteLine("Animal在{0}點Sleep", time);          return time;      }      public virtual void EatFood()      {          Console.WriteLine("Animal EatFood");      }  }  public class Cat : Animal  {      public override void EatFood()      {          Console.WriteLine("Cat EatFood");          //base.EatFood();      }      new public void Sleep()      {          Console.WriteLine("Cat Sleep");      }      //public new void Sleep()      //{      //    Console.WriteLine("Cat Sleep");      //}  }  public class Dog : Animal  {      public override void EatFood()      {          Console.WriteLine("Dog EatFood");          //base.EatFood();      }  }

          下面來看看需要執(zhí)行的代碼:

          class Program  {      static void Main(string[] args)      {          //Animal的實例          Animal a = new Animal();          //Animal的實例,引用派生類Cat對象          Animal ac = new Cat();          //Animal的實例,引用派生類Dog對象          Animal ad = new Dog();          //Cat的實例          Cat c = new Cat();          //Dog的實例          Dog d = new Dog();          //重載          a.Sleep();          a.Sleep(23);          //重寫和虛方法          a.EatFood();          ac.EatFood();          ad.EatFood();          //抽象方法          a.Live();          //隱藏方法          a.Sleep();          ac.Sleep();          c.Sleep();          Console.ReadKey();      }  }

          首先,我們定義了幾個我們需要使用的類的實例,需要注意的是:

          • (1)Biology類是抽象類,無法實例化;
          • (2)變量ac是Animal的實例,但是指向一個Cat的對象。因為Cat類型是Animal類型的派生類,所以這種轉(zhuǎn)換沒有問題。這也是多態(tài)性的重點。

          下面我們來一步一步的分析:

          1、

          //重載  a.Sleep();  a.Sleep(23);

          很明顯,Animal 的變量 a 調(diào)用的兩個 Sleep 方法是重載的方法,第一句調(diào)用的是無參數(shù)的 Sleep() 方法,第二句調(diào)用的是有一個 int 參數(shù)的 Sleep 方法。注意兩個 Sleep 方法的返回值不一樣,這也說明了重寫的三個特征中的最后一個特征——返回值可以不相同。

          運行的結(jié)果如下:

          Animal Sleep  Animal在23點Sleep

          2、

          //重寫和虛方法  a.EatFood();  ac.EatFood();  ad.EatFood();

          在這一段中,a、ac 以及 ad 都是 Animal 的實例,但是他們引用的對象不同,a 引用的是 Animal 對象,ac 引用的是 Cat 對象,ad 引用的是 Dog 對象,這個差別會造成執(zhí)行結(jié)果的什么差別呢,請看執(zhí)行結(jié)果:

          Animal EatFood  Cat EatFood  Dog EatFood

          第一句 Animal 實例,直接調(diào)用 Animal 的虛方法 EatFood,沒有任何問題。

          在第二、三句中,雖然同樣是 Animal 的實例,但是他們分別指向 Cat 和 Dog 對象,所以調(diào)用的 Cat 類和 Dog 類中各自重寫的 EatFood 方法,就像是 Cat 實例和 Dog 實例直接調(diào)用 EatFood 方法一樣。這個也就是多態(tài)性的體現(xiàn):同一操作作用于不同的對象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果。

          3、

          //抽象方法  a.Live();

          這個比較簡單,就是直接重寫父類 Biology 中的 Live 方法,執(zhí)行結(jié)果如下:

          Animal重寫的Live

          4、

          //隱藏方法  a.Sleep();  ac.Sleep();  c.Sleep();

          在分析隱藏方法時要和虛方法、重寫相互比較。變量 a 調(diào)用 Animal 類的 Sleep 方法以及變量 c 調(diào)用 Cat 類的 Sleep 方法沒有異議,但是變量 ac 引用的是一個 Cat 類型的對象,它應(yīng)該調(diào)用 Animal 類型的 EatFood 方法呢,還是 Cat 類型的 EatFood 方法呢?答案是調(diào)用父類即 Animal 的 EatFood 方法。執(zhí)行結(jié)果如下:

          Animal Sleep  Animal Sleep  Cat Sleep

          大多數(shù)的文章都是介紹到這里為止,僅僅是讓我們知道這些概念以及調(diào)用的方法,而沒有說明為什么會這樣。下面我們就來深入一點,談?wù)劧鄳B(tài)背后的機理。


          二、深入理解多態(tài)性

          要深入理解多態(tài)性,就要先從值類型和引用類型說起。我們都知道值類型是保存在線程棧上的,而引用類型是保存在托管堆中的。因為所有的類都是引用類型,所以我們僅僅看引用類型。

          現(xiàn)在回到剛才的例子,Main 函數(shù)時程序的入口,在 JIT 編譯器將 Main 函數(shù)編譯為本地CPU指定時,發(fā)現(xiàn)該方法引用了 Biology、Animal、Cat、Dog這幾個類,所以 CLR 會創(chuàng)建幾個實例來表示這幾個類型本身,我們把它稱之為”類型對象”。該對象包含了類中的靜態(tài)字段,以及包含類中所有方法的方法表,還包含了托管堆中所有對象都要有的兩個額外的成員——類型對象指針(Type Object Point)和同步塊索引(sync Block Index)。

          可能上面這段對于有些沒有看過相關(guān)CLR書籍的童鞋沒有看懂,所以我們畫個圖來描述一下:

          C# 中的多態(tài)性

          上面的這個圖是在執(zhí)行 Main 函數(shù)之前 CLR 所做的事情,下面開始執(zhí)行 Main 函數(shù)(方便起見,簡化一下 Main 函數(shù)):

          //Animal的實例  Animal a = new Animal();  //Animal的實例,引用派生類Cat對象  Animal ac = new Cat();  //Animal的實例,引用派生類Dog對象  Animal ad = new Dog();  a.Sleep();  a.EatFood();  ac.EatFood();  ad.EatFood();

          下面實例化三個 Animal 實例,但是他們實際上指向的分別是 Animal 對象、Cat 對象和 Dog 對象,如下圖:

          C# 中的多態(tài)性

          請注意,變量 ac 和 ad 雖然都是 Animal 類型,但是指向的分別是 Cat 對象和 Dog 對象,這里是關(guān)鍵。

          當(dāng)執(zhí)行 a.Sleep() 時,由于 Sleep 是非虛實例方法,JIT 編譯器會找到發(fā)出調(diào)用的那個變量(a)的類型(Animal)對應(yīng)的類型對象(Animal 類型對象)。然后調(diào)用該類型對象中的 Sleep 方法,如果該類型對象沒有 Sleep 方法,JIT 編譯器會回溯類的基類(一直到 Object)中查找 Sleep 方法。

          當(dāng)執(zhí)行 ac.EatFood 時,由于 EatFood 是虛實例方法,JIT 編譯器調(diào)用時會在方法中生成一些額外的代碼,這些代碼會首先檢查發(fā)出調(diào)用的變量(ac),然后跟隨變量的引用地址找到發(fā)出調(diào)用的對象(Cat 對象),找到發(fā)出調(diào)用的對象對應(yīng)的類型對象(Cat 類型對象),最后在該類型對象中查找 EatFood 方法。同樣的,如果在該類型對象中沒有查找到 EatFood 方法,JIT 編譯器會回溯到該類型對象的基類中查找。

          上面描述的就是 JIT 編譯器在遇到調(diào)用類型的非虛實例方法以及虛實例方法時的不同執(zhí)行方式,也這是處理這兩類方法的不同方式造成了表面上我們看到的面向?qū)ο蟮娜齻€特征之一——多態(tài)性。

          原文地址:https://www.cnblogs.com/zhangkai2237/archive/2012/12/20/2826734.html

          贊(0)
          分享到: 更多 (0)
          網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號