《隨筆十六》——C#中的 “ 虛擬函式 ”
目錄
使用基類的引用
● 派生類的例項由基類的例項加上派生類新增的成員組成。 派生類的引用指向整個類物件,包括基類部分。
如果有一個派生類物件, 就可以獲取派生類物件的基類部分的引用。
● 下面的程式碼示例 使用派生類物件的基類部分的引用來訪問物件。
namespace HelloWorld_Console { class SomeClass {// 當基類中的欄位是私有的,那麼在派生類中定義同名欄位, 該欄位不需要添new,如果是其它的訪問修飾符就需要新增new // 以上規則對基類中的成員函式都一樣 int m_Field = 0; public void Print() { WriteLine("呼叫的是基類中的Print函式!"); } public SomeClass() { WriteLine("呼叫的是基類中建構函式!"); } public void Show() { WriteLine($"輸出基類的m_Field的值:{m_Field}"); } } class OtherClass : SomeClass { new public void Print() { WriteLine("呼叫的是派生類的Print函式!"); } public OtherClass() { WriteLine("呼叫的是派生類中建構函式!"); } new public void Show() { WriteLine($"輸出派生類m_Field的值:{m_Field}"); base.Show(); //派生類中呼叫基類中的該函式 } int m_Field = 99; } class Program { static void Main(string[] args) { OtherClass myOtherClass = new OtherClass(); SomeClass mySomeClass = (SomeClass)myOtherClass; //該程式碼不會呼叫基類的建構函式 myOtherClass.Show(); mySomeClass.Print(); ReadKey(); } } } 輸出結果為: 呼叫的是基類中建構函式! 呼叫的是派生類中建構函式! 輸出派生類m_Field的值:99 輸出基類的m_Field的值:0 呼叫的是基類中的Print函式!
當派生類中隱藏了基類中的成員, 使用基類物件引用派生類物件時, 然後用基類物件訪問同名成員,訪問的是基類中的同名成員。
虛方法 和覆寫方法
● 使用虛擬函式可以使基類物件引用派生類物件時, 然後用基類物件訪問同名成員,訪問的是派生類中的同名成員。
可以使用基類引用呼叫派生類的方法,只需要滿足下面的條件:
派生類中的方法和基類的方法有相同的簽名和返回型別。
基類的方法使用virtual標識。
派生類的方法使用override 標識。
namespace HelloWorld_Console { class SomeClass {// 當基類中的欄位是私有的,那麼在派生類中定義同名欄位, 該欄位不需要添new,如果是其它的訪問修飾符就需要新增new // 以上規則對基類中的成員函式都一樣 int m_Field = 0; public SomeClass() { WriteLine("呼叫的是基類中建構函式!"); } virtual public void Print() { WriteLine("呼叫的是基類中的Print函式!"); } virtual public void Show() { WriteLine($"輸出基類的m_Field的值:{m_Field}"); } } class OtherClass : SomeClass { override public void Print() { WriteLine("呼叫的是派生類的Print函式!"); } public OtherClass() { WriteLine("呼叫的是派生類中建構函式!"); } override public void Show() { WriteLine($"輸出派生類m_Field的值:{m_Field}"); base.Show(); //派生類中呼叫基類中的該函式 } int m_Field = 99; } class Program { static void Main(string[] args) { OtherClass myOtherClass = new OtherClass(); SomeClass mySomeClass = (SomeClass)myOtherClass; //該程式碼不會呼叫基類的建構函式 myOtherClass.Show(); mySomeClass.Print(); //現在這裡呼叫的就是派生類中的同名函式 ReadKey(); } } } 輸出結果為: 呼叫的是基類中建構函式! 呼叫的是派生類中建構函式! 輸出派生類m_Field的值:99 輸出基類的m_Field的值:0 呼叫的是派生類的Print函式!
● 關於使用 virtual 和 override 關鍵字應注意的有:
如果在基類中使用 virtual 關鍵字宣告某個函式為虛擬函式, 那麼在派生類中的原型相同的同名函式如果沒有用override 標識,那麼必須用 new 做前輟,否則的話, 派生類中的同名函式的簽名(成員函式的簽名由 名稱和引數列表組成, 不包含返回型別。)必須不一樣。
基類中被覆蓋的方法不能是private的,但是可以是其他的訪問性修飾符的, 而覆寫方法一般來說是public的, 當是也可以是其他修飾符的任何一種, 如果是後者就不能再類外面訪問該函數了, 只能間接呼叫。 所以說覆寫方法一般來說是public的,這樣在外面就可以呼叫了。
不能覆寫static 方法 或 建構函式 、解構函式, 每一個類都應有有自己的建構函式 和解構函式。
方法 、事件、屬性、索引器都可以被宣告為 virtual 和 override , 它們也可以被 隱藏(new)。
覆寫標記為override 的方法
● override 方法 可以在繼承的任何層次上出現。
當使用物件基類部分的引用呼叫一個覆寫的方法時, 方法的呼叫被沿派生層次上溯執行, 一直標記為 override 的方法的最高派生類版本。
如果在更高的派生級別有該方法的另外宣告,但沒有被標記為override, 那麼他們就不會被呼叫。
namespace HelloWorld_Console
{
class SomeClass
{// 當基類中的欄位是私有的,那麼在派生類中定義同名欄位, 該欄位不需要添new,如果是其它的訪問修飾符就需要新增new
// 以上規則對基類中的成員函式都一樣
int m_Field = 0;
public SomeClass()
{
WriteLine("呼叫的是基類中建構函式!\n");
}
virtual public void Print()
{
WriteLine("呼叫的是基類中的Print函式!\n");
}
virtual public void Show()
{
WriteLine($"輸出基類的m_Field的值:{m_Field}\n");
}
}
class OtherClass : SomeClass
{
override public void Print()
{
WriteLine("呼叫的是派生類OtherClass的Print函式!\n");
}
public OtherClass()
{
WriteLine("呼叫的是派生類OtherClass中建構函式!\n");
}
override public void Show()
{
WriteLine($"輸出派生類OtherClass中m_Field的值:{m_Field}\n");
base.Show(); //派生類中呼叫基類中的該函式
}
int m_Field = 99;
}
class SecondDerived : OtherClass
{
int m_Field = 999;
override public void Print()
{
WriteLine("呼叫的是派生類SecondDerived的Print函式!\n");
}
public SecondDerived()
{
WriteLine("呼叫的是派生類SecondDerived中建構函式!\n");
}
override public void Show()
{
WriteLine($"輸出派生類SecondDerived中的m_Field的值:{m_Field}\n");
base.Show(); //派生類中呼叫基類中的該函式
}
}
class Program
{
static void Main(string[] args)
{
OtherClass myOtherClass = new OtherClass();
SecondDerived mySecondDerived = new SecondDerived();
SomeClass mySomeClass = (SomeClass)myOtherClass; //該程式碼不會呼叫基類的建構函式,或者說根本不會呼叫任何的建構函式
myOtherClass.Show();
mySecondDerived.Print();
mySomeClass.Print(); //現在這裡呼叫的就是派生類中的同名函式
ReadKey();
}
}
}
輸出結果為:
呼叫的是基類中建構函式!
呼叫的是派生類OtherClass中建構函式!
呼叫的是基類中建構函式!
呼叫的是派生類OtherClass中建構函式!
呼叫的是派生類SecondDerived中建構函式!
輸出派生類OtherClass中m_Field的值:99
輸出基類的m_Field的值:0
呼叫的是派生類SecondDerived的Print函式!
呼叫的是派生類OtherClass的Print函式!
● 把上面的 SecondDerived 類中有 override 函式 改成前輟 new,看有什麼結果:
namespace HelloWorld_Console
{
class SomeClass
{// 當基類中的欄位是私有的,那麼在派生類中定義同名欄位, 該欄位不需要添new,如果是其它的訪問修飾符就需要新增new
// 以上規則對基類中的成員函式都一樣
int m_Field = 0;
public SomeClass()
{
WriteLine("呼叫的是基類中建構函式!\n");
}
virtual public void Print()
{
WriteLine("呼叫的是基類中的Print函式!\n");
}
virtual public void Show()
{
WriteLine($"輸出基類的m_Field的值:{m_Field}\n");
}
}
class OtherClass : SomeClass
{
override public void Print()
{
WriteLine("呼叫的是派生類OtherClass的Print函式!\n");
}
public OtherClass()
{
WriteLine("呼叫的是派生類OtherClass中建構函式!\n");
}
override public void Show()
{
WriteLine($"輸出派生類OtherClass中m_Field的值:{m_Field}\n");
base.Show(); //派生類中呼叫SomeClass中的該函式
}
int m_Field = 99;
}
class SecondDerived : OtherClass
{
int m_Field = 999;
new public void Print()
{
WriteLine("呼叫的是派生類SecondDerived的Print函式!\n");
}
public SecondDerived()
{
WriteLine("呼叫的是派生類SecondDerived中建構函式!\n");
}
new public void Show()
{
WriteLine($"輸出派生類SecondDerived中的m_Field的值:{m_Field}\n");
base.Show(); //派生類中呼叫SecondDerived的該函式
}
}
class Program
{
static void Main(string[] args)
{
OtherClass myOtherClass = new OtherClass();
SecondDerived mySecondDerived = new SecondDerived();
SomeClass mySomeClass = (SomeClass)myOtherClass;
myOtherClass.Show();
mySecondDerived.Show();
mySomeClass.Show(); //這裡呼叫的是OtherClass 的Show函式,如果OtherClass 函式也是new前輟,那麼執行SomeClass 類中的該函式
ReadKey();
}
}
}
輸出結果為:
呼叫的是基類中建構函式!
呼叫的是派生類OtherClass中建構函式!
呼叫的是基類中建構函式!
呼叫的是派生類OtherClass中建構函式!
呼叫的是派生類SecondDerived中建構函式!
輸出派生類OtherClass中m_Field的值:99
輸出基類的m_Field的值:0
輸出派生類SecondDerived中的m_Field的值:999
輸出派生類OtherClass中m_Field的值:99
輸出基類的m_Field的值:0
輸出派生類OtherClass中m_Field的值:99
輸出基類的m_Field的值:0
覆蓋屬性
namespace HelloWorld_Console
{
class SomeClass
{// 當基類中的欄位是私有的,那麼在派生類中定義同名欄位, 該欄位不需要添new,如果是其它的訪問修飾符就需要新增new
// 以上規則對基類中的成員函式都一樣
private int _myInt = 5;
virtual public int MyProperty
{
set
{
if (value != 0)
{
_myInt = value;
}
}
get
{
return _myInt;
}
}
}
class MyDerivedClas: SomeClass
{
private int _myInt = 10;
override public int MyProperty
{
set
{
if (value != 0)
{
_myInt = value;
}
}
get
{
return _myInt;
}
}
}
class Program
{
static void Main(string[] args)
{
MyDerivedClas derived = new MyDerivedClas();
SomeClass myBase = new SomeClass();
derived.MyProperty = 20; //設定屬性的值
myBase.MyProperty = 200;
WriteLine($"輸出derived.MyProperty的值 { derived.MyProperty}");
WriteLine($"輸出 myBase.MyProperty的值 { myBase.MyProperty}");
SomeClass mybc = (SomeClass)derived; //基類物件引用派生類物件
WriteLine($"\n輸出derived.MyProperty的值 { derived.MyProperty}");
WriteLine($"輸出mybc.MyProperty的值 { mybc.MyProperty}"); //呼叫的是派生類中的屬性
ReadKey();
}
}
}
輸出結果為:
輸出derived.MyProperty的值 20
輸出 myBase.MyProperty的值 200
輸出derived.MyProperty的值 20
輸出mybc.MyProperty的值 20
建構函式初始化語句
● 預設情況下, 在構造物件時,將呼叫的是基類的無引數建構函式。 如果希望派生類使用一個指定的基類建構函式而不是無引數建構函式,必須在建構函式初始化語句中指定它。
有兩種形式的建構函式初始化語句:
使用關鍵字base 並指明使用哪一個基類建構函式。
使用關鍵字this 並指明應該使用當前類的哪個類建構函式 —— (相當於C++中的委託建構函式)。
注意: 在基類引數列表中的引數必須在型別和順序方面與已定的基類建構函式的引數列表相匹配。
namespace HelloWorld_Console
{
class MyClass
{
readonly int firstVar;
readonly double secondVar;
int UserIdNumber;
MyClass()
{
firstVar = 20;
secondVar = 400.3;
WriteLine("私有的MyClass 建構函式被呼叫!");
}
public MyClass(int firstName):this()
{
UserIdNumber = firstName;
WriteLine("公有的MyClass 建構函式被呼叫!");
}
public int getValue
{
get
{
return UserIdNumber;
}
}
}
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass(55);
WriteLine($"輸出UserIdNumber的值:{mc.getValue}");
ReadKey();
}
}
}
輸出結果為:
私有的MyClass 建構函式被呼叫!
公有的MyClass 建構函式被呼叫!
輸出UserIdNumber的值:55