new、vitual、override之間那點事

    1)第一種情況:子類某個方法使用new修飾,但父類中並沒有該方法。
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }
  14.     public class Son : Father
  15.     {
  16.         new public void SonSay() 
  17.         {
  18.             Console.WriteLine("Son.SonSay()");
  19.         }
  20.     }
  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father father = new Father();
  26.             Son son = new Son();
  27.             father.Say();
  28.             son.SonSay();
  29.             Console.ReadLine();
  30.         }
  31.     }
  32. }
    Son類繼承自Father,Father類中並沒有SonSay()這個方法,在Son類中定義SonSay()方法,並且定義爲new的,結果會怎麼樣呢?
    運行結果:
    Father.Say()
    Son.SonSay()
    即運行結果沒問題,但編譯器會提出一個警告:子類Son中的方法SonSay()在父類中並不存在,沒必要使用new關鍵字。
    2)第二種情況:父類中有一個方法,子類中也有一個同樣簽名的方法,但父類中該方法沒有使用使用virtual、override、abstract修飾,子類中該方法也沒有使用new或者override修飾。
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }
  14.     public class Son : Father
  15.     {
  16.          public void Say() 
  17.         {
  18.             Console.WriteLine("Son.Say()");
  19.         }
  20.     }
  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father[] persons = new Father[2];
  26.             persons[0] = new Father();
  27.             persons[1] = new Son();
  28.             persons[0].Say();
  29.             persons[1].Say();
  30.             Console.ReadLine();
  31.         }
  32.     }
  33. }
    運行結果:
    Father.Say()
    Father.Say()
    爲什麼persons[1].Say()的運行結果也是Father.Say()呢?person[1]的實例類型明明是Son啊。因爲在c#中,如果子類中的方法簽名如果和父類中的方法簽名一致而不是override的(名字相同,參數格式和類型也一致,跟返回值沒關),那麼就自動隱式的標記爲new的方法了。而new出來的方法是不支持運行時綁定的,它是編譯時綁定,即跟聲明時類型有關,而跟運行時該對象的實際類型無關,即編譯時已經確定是調用父類的還是子類的方法了。
另外編譯器會給出一個警告:該方法簽名跟父類的一樣,如果是有意new的話,請加上new關鍵字。
    3)第三種情況:子類的某個方法直接標識爲override的,而父類該方法並沒有聲明爲vitual、abstract或者override的。
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;

  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }

  14.     public class Son : Father
  15.     {
  16.          public override void Say() 
  17.         {
  18.             Console.WriteLine("Son.Say()");
  19.         }
  20.     }

  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father[] persons = new Father[2];

  26.             persons[0] = new Father();
  27.             persons[1] = new Son();

  28.             persons[0].Say();
  29.             persons[1].Say();

  30.             Console.ReadLine();
  31.         }
  32.     }
  33. }
    這次運行結果會怎麼樣呢?編譯器這次直接報錯了,而不警告:
'NewVituslOverride.Son.Say()': cannot override inherited member 'NewVituslOverride.Father.Say()' because it is not marked virtual, abstract, or override。
    編譯錯誤內容很清楚,我就不解釋了。
    本例子說明override不是隨便用的,必須父類的方法、屬性、索引、事件是abstract、vitual或者override的時候,子類才能override它。
    4)第四種情況:子類new父類中的方法。
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;

  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }

  14.     public class Son : Father
  15.     {
  16.          public new void Say() 
  17.         {
  18.             Console.WriteLine("Son.Say()");
  19.         }
  20.     }

  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father[] persons = new Father[2];

  26.             persons[0] = new Father();
  27.             persons[1] = new Son();

  28.             persons[0].Say();
  29.             persons[1].Say();

  30.             Console.ReadLine();
  31.         }
  32.     }
  33. }
    運行結果:
    Father.Say()
    Father.Say()
    如果第二種情況看懂了,就知道這個例子的運行結果和第二種情況一樣的。只是這次代碼用new把意思說的明明白白,子類我就是要隱藏父類的方法Say()。由於person[1]的聲明類型是Father(雖然實際類型是Son),所以還是因爲編譯時綁定的原因,person[1].Say()跑出來的結果還是Father.Say()。
    5)父類方法使用vitual,子類使用override重寫
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public virtual void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }
  14.     public class Son : Father
  15.     {
  16.          public override void Say() 
  17.         {
  18.             Console.WriteLine("Son.Say()");
  19.         }
  20.     }
  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father[] persons = new Father[2];
  26.             persons[0] = new Father();
  27.             persons[1] = new Son();
  28.             persons[0].Say();
  29.             persons[1].Say();
  30.             Console.ReadLine();
  31.         }
  32.     }
  33. }
    運行結果:
    Father.Say()
    Son.Say()
    這次雖然persons[1]的聲明類型是Father,但因爲Say()是vitual的,需要運行時根據對象的實際類型來調用,因爲運行時person[1]的實際類型是Son,所以運行的是Son的Say()。
我們來仔細看下這個例子生成的IL代碼:
    Father類:
  1. .class public auto ansi beforefieldinit Father
  2.     extends [mscorlib]System.Object
  3. {
  4.     .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
  5.     {
  6.         .maxstack 8
  7.         L_0000: ldarg.0 
  8.         L_0001: call instance void [mscorlib]System.Object::.ctor()
  9.         L_0006: ret 
  10.     }
  11.     .method public hidebysig newslot virtual instance void Say() cil managed
  12.     {
  13.         .maxstack 8
  14.         L_0000: nop 
  15.         L_0001: ldstr "Father.Say()"
  16.         L_0006: call void [mscorlib]System.Console::WriteLine(string)
  17.         L_000b: nop 
  18.         L_000c: ret 
  19.     }
  20. }
    從裏面可以看出Father類的Say()方法,被標識爲newslot virtual的了。
    我們接着來看Son類的IL代碼:
  1. .class public auto ansi beforefieldinit Son
  2.     extends NewVituslOverride.Father
  3. {
  4.     .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
  5.     {
  6.         .maxstack 8
  7.         L_0000: ldarg.0 
  8.         L_0001: call instance void NewVituslOverride.Father::.ctor()
  9.         L_0006: ret 
  10.     }
  11.     .method public hidebysig virtual instance void Say() cil managed
  12.     {
  13.         .maxstack 8
  14.         L_0000: nop 
  15.         L_0001: ldstr "Son.Say()"
  16.         L_0006: call void [mscorlib]System.Console::WriteLine(string)
  17.         L_000b: nop 
  18.         L_000c: ret 
  19.     }
  20. }
    從Son的IL代碼中我們可以看出來,雖然我們在c#代碼中聲明Son類的Say()方法爲override的,但實際上轉換爲IL代碼後,該方法在Son類中是virtual的。
    接着我們來看下Program類的IL代碼:
  1. .class private auto ansi beforefieldinit Program
  2.     extends [mscorlib]System.Object
  3. {
  4.     .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
  5.     {
  6.         .maxstack 8
  7.         L_0000: ldarg.0 
  8.         L_0001: call instance void [mscorlib]System.Object::.ctor()
  9.         L_0006: ret 
  10.     }
  11.     .method private hidebysig static void Main(string[] args) cil managed
  12.     {
  13.         .entrypoint
  14.         .maxstack 3
  15.         .locals init (
  16.             [0] class NewVituslOverride.Father[] persons)
  17.         L_0000: nop 
  18.         L_0001: ldc.i4.2 
  19.         L_0002: newarr NewVituslOverride.Father
  20.         L_0007: stloc.0 
  21.         L_0008: ldloc.0 
  22.         L_0009: ldc.i4.0 
  23.         L_000a: newobj instance void NewVituslOverride.Father::.ctor()
  24.         L_000f: stelem.ref 
  25.         L_0010: ldloc.0 
  26.         L_0011: ldc.i4.1 
  27.         L_0012: newobj instance void NewVituslOverride.Son::.ctor()
  28.         L_0017: stelem.ref 
  29.         L_0018: ldloc.0 
  30.         L_0019: ldc.i4.0 
  31.         L_001a: ldelem.ref 
  32.         L_001b: callvirt instance void NewVituslOverride.Father::Say()
  33.         L_0020: nop 
  34.         L_0021: ldloc.0 
  35.         L_0022: ldc.i4.1 
  36.         L_0023: ldelem.ref 
  37.         L_0024: callvirt instance void NewVituslOverride.Father::Say()
  38.         L_0029: nop 
  39.         L_002a: call string [mscorlib]System.Console::ReadLine()
  40.         L_002f: pop 
  41.         L_0030: ret 
  42.     }
  43. }
    構造函數就不用看了,主要是看Main函數及其裏面的執行順序。
    在Main函數中,先聲明瞭一個Father[]數組,接着設置該數組的長度爲2,接着創建了一個Father對象和一個Son對象。
    關鍵是這兩個對象調用Say()方法。我們可以看到是使用callvirl命令的,而不是call命令,使用callvirl命令就要求運行時檢查對象的實際類型,然後調用該類型的對應方法。

發佈了29 篇原創文章 · 獲贊 1 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章