程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .NET中的虛函數

.NET中的虛函數

編輯:關於.NET

面向對象的程序設計有三大要素,封裝、繼承和多態。虛函數是多態的重要組成部分,同時又在類的繼承關系中有著很多變化。本文討論.NET中對虛函數的支持。

首先,我們通過一個例子來看看虛函數的普通用法:

class CA {
    public virtual void Foo() {
      Console.WriteLine("CA.Foo");
    }
  }

  class CB : CA {
    public override void Foo() {
      Console.WriteLine("CB.Foo");
    }
}

class Test {
    public static void InvokeFoo(CA ca)  {
      ca.Foo();
    }
    public static void Main()  {
      InvokeFoo(new CB());
    }
}

輸出結果

CB.Foo

在這個例子中,盡管在調用InvokeFoo()的時候,CB被轉換成CA,但是當執行ca.Foo的時候,仍然調用了CB的Foo。因為ca此時指向的是一個CB類型的對象。這種調用模式,我們稱之為運行時綁定。因為在編譯InvokeFoo時,編譯器無法獲取參數ca的真實類型,只有在運行的時候,才能根據ca的真實類型,決定調用哪一個函數。

在這個例子中,兩個關鍵字值得我們注意,首先是virtual,他告訴編譯器,當前函數需要運行時綁定。其次是override,他告訴編譯器,我要覆蓋基類中的Foo()。

看到這裡,可能讀者會對兩個問題持有疑惑:

[問題]: 不用virtual結果如何?

[問題]: 不用override結果如何?

讀者不妨自己動手修改上例,嘗試這兩個關鍵字的不同組合,看看輸出的結果如何。在這裡,我僅給出組合條件和其輸出結果。

序號 基類(CA)中是否有virtual 子類(CB)中是否有override 輸出 1 是 是 CB.Foo 2 是 否 CA.Foo 3 否 是 編譯錯誤 4 否 否 CA.Foo

我希望通過對這組實驗結果的解釋,交待一些.NET中虛函數的相關概念。

運行時綁定僅體現在虛函數中。因此在試驗4中,輸出的結果是CA.Foo。因為Foo沒有被申明為virtual,在編譯階段,已經把ca.Foo綁定到CA.Foo。

Override只能用於虛函數中。當子類繼承基類,他便擁有了基類所有的函數,Override修飾的函數,將替換基類原來的函數。否則,子類會新增加一個函數,並同時保留基類中的函數。 下面的這個例子,很好的說明了這個問題

class CA {
    public virtual void Foo() {
      Console.WriteLine("CA.Foo");
    }
  }

  class CB : CA {
    public override void Foo()  {
      Console.WriteLine("CB.Foo");
    }
  }

  class CC : CA  {
    public new void Foo()  {
      Console.WriteLine("CC.Foo");
    }
  }

  class Test  {
    public static void Main()  {
      Console.WriteLine(typeof(CB).GetMethods().Length);  // 輸出5
      Console.WriteLine(typeof(CC).GetMethods().Length); // 輸出6
    }
  }

這段程序輸出CB和CC的函數個數,CB的5個函數中,4個來自於Sysetm.Object,剩下的一個就是Foo。CC中多了一個函數,因為使用了new (如果不使用new,也是相同的結果,因為C#編譯器默認使用new,但不顯示指明new會給出一個警告),說明了CC.Foo是一個不同於CA.Foo的虛函數。

所以,在試驗2中,不使用override,我們在InvokeFoo中調用的還是CA.Foo()。雖然這個時候還是運行時綁定,但是因為CB.Foo並沒有覆蓋CA.Foo,因此我們還是得到了基類的實現。

當一個函數不是虛函數的時候,子類中相同簽名的函數總是覆蓋了父類中的函數,並不需要override關鍵字。所以c#編譯器會把它當作一個錯誤,如上表中試驗3所示。

如果讀者理解了上面的內容,那麼來看看一個略微復雜的情況:我們邀請interface出場!

interface IA  {
    void Foo();
}
  class CA: IA {
    public void Foo() {
      Console.WriteLine("CA.Foo");
    }
}

[問題]: Foo是虛函數嗎?

答案是肯定的,就像interface方法不能顯示聲明為public一樣,我們也不能在IA.Foo前面加上virtual。原因很簡單,所有的interface方法都是虛函數!在調用interface方法的時候,總是要使用運行時綁定。

[問題]: CA實現IA,那麼CA.Foo前面需要override嗎?

答案是否定的,在C#中,繼承和實現是截然不同的兩個概念,盡管在語法上很相似。繼承意味著全盤接收基類的函數,而實現只是一個契約,保證當前類會提供interface中聲明的函數,而不會接受基類的函數(事實上也不能,因為interface中沒有函數的實現)

[問題]: CA實現IA,那麼CA.Foo前面需要virtual嗎?

答案是需要的,否則的話,CA的子類將無法覆寫Foo,下面的代碼是CA.Foo的IL聲明,我們發現了關鍵字final(注:這裡的final是IL語言的關鍵字,和C#中sealed有些類似,意味著子類不能override當前函數)

.method public hidebysig newslot virtual final

instance void Foo() cil managed

下面一段代碼緊接著上面的代碼,讀者可以猜測一下輸出,看看是否掌握了本文今天講述的內容,我會在下期博客中講解其原委,並且和大家進一步通過IL來研究.NET中的虛函數。

class CB : CA, IA {
    public void Foo()  {
      Console.WriteLine("CB.Foo");
    }
}
class Test  {
    public static void InvokeFoo(CA ia) {
      ca.Foo();
    }
    public static void Main()  {
      InvokeFoo(new CA());
      InvokeFoo(new CB());
     }

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved