C# 默認情況類中的成員都是非虛擬的;
將類中的成員定義成虛擬的,表示這些成員將會在繼承后重寫其中的內容。
virtual 關鍵字能修飾方法、屬性、索引器以及事件等,用到父類的成員中。
使用 virtual 關鍵字修飾屬性和方法的語法形式如下。
//修飾屬性
public virtual 數據類型 屬性名{get; set; }
//修飾方法
訪問修飾符 virtual 返回值類型方法名
{
語句塊;
}
需要注意的是,virtual 關鍵字不能修飾使用 static 修飾的成員。
此外,virtual 關鍵字既可以添加到訪問修飾符的后面,也可以添加到訪問修飾符的前面,但實際應用中習慣將該關鍵字放到訪問修飾符的后面。
子類繼承父類后能重寫父類中的成員,重寫的關鍵字是 override。
所謂重寫是指子類和父類的成員定義一致,僅在子類中增加了 override 關鍵字修飾成員。
例如在父類中有一個求長方形面積的方法,方法定義如下。
- publie int Area(int x, int y)
- {
- return x * y
- }
在子類中重寫該方法的代碼如下。
public override int Area(int x,int y)
{
語句塊;
return 整數類型的值;
}
在子類中重寫父類中的方法后能改變方法體中的內容,但是方法的定義不能改變。
【實例 1】將上一節《C# base》中定義的 Person 類中的 Print 方法更改為虛擬的方法,分別用 Student 類和 Teacher 類繼承 Person 類,並重寫 Print 方法,打印出學生信息和教師信息。
為了減少重復的代碼,在每個類中省略了屬性部分的定義內容,僅保留 Print 方法部分的內容,實現的代碼如下。
- class Person
- {
- public virtual void Print()
- {
- Console.WriteLine("編號:"+ Id);
- Console.WriteLine("姓名:"+ Name);
- Console.WriteLine("性別:"+ Sex);
- Console.WriteLine("身份證號:"+ Cardid);
- Console.WriteLine("聯系方式:"+ Tel);
- }
- }
- class Student:Person
- {
- public override void Print()
- {
- Console.WriteLine("編號:"+ Id);
- Console.WriteLine("姓名:"+ Name);
- Console.WriteLine("性別:"+ Sex);
- Console.WriteLine("身份證號:"+ Cardid);
- Console.WriteLine("聯系方式:"+ Tel);
- Console.WriteLine("專業:"+ Major);
- Console.WriteLine("年級:"+ Grade);
- }
- }
- class Teacher:Person
- {
- public override void Print()
- {
- Console.WriteLine("編號:"+ Id);
- Console.WriteLine("姓名:"+ Name);
- Console.WriteLine("性別:"+ Sex);
- Console.WriteLine("身份證號:"+ Cardid);
- Console.WriteLine("聯系方式:"+ Tel);
- Console.WriteLine("專業:"+ Major);
- Console.WriteLine("年級:"+ Grade);
- }
- }
通過上面的代碼即可完成對 Person 類中 Print 方法的重寫,在重寫后的 Print 方法中能直接調用在 Person 類中定義的成員。
但讀者會發現在 Person 類的 Print 中已經對 Person 中的相關屬性編寫了輸出操作的代碼,而每一個子類中又重復地編寫了代碼,造成代碼的冗余,也沒有體現出代碼重用的特點。
如果能在重寫父類方法的同時直接使用父類中已經編寫過的內容就會方便很多。
在重寫 Print 方法后仍然需要使用 base 關鍵字調用父類中的 Print 方法執行相應的操作。
【實例 2】改寫實例 1 中的 Student 和 Teacher 類中重寫的 Print 方法,使用 base 關鍵字調用父類中的 Print 方法。
根據題目要求,更改后的代碼如下。
- class Student:Person
- {
- public override void Print()
- {
- base.Print ();
- Console.WriteLine("專業:"+ Major);
- Console.WriteLine("年級:"+ Grade);
- }
- }
- class Teacher:Person
- {
- public override void Print()
- {
- base.Print ();
- Console.WriteLine("專業:"+ Major);
- Console.WriteLine("年級:"+ Grade);
- }
- }
從上面的代碼可以看出繼承給程序帶來的好處,不僅減少了代碼的冗余,還增強了程序的可讀性。
方法隱藏和重寫方法有區別嗎?這是很多初學者常問的問題。觀察以下代碼,思考結果會是什么?
- class Program
- {
- static void Main(string[] args)
- {
- A a1 = new B();
- a1.Print();
- A a2 = new C();
- a2.Print();
- }
- }
- class A
- {
- public virtual void Print()
- {
- Console.WriteLine("A");
- }
- }
- class B :A
- {
- public new void Print()
- {
- Console.WriteLine("B");
- }
- }
- class C :A
- {
- public override void Print()
- {
- Console.WriteLine("C");
- }
- }
執行上面的代碼,效果如下圖所示。
從上面的執行效果可以看出,使用方法隱藏的方法調用的結果是父類 A 中 Print 方法中的內容,而使用方法重寫的方法調用的結果是子類 C 中 Print 方法中的內容。
因此方法隱藏相當於在子類中定義新方法,而方法重寫則是重新定義父類中方法的內容。
從上面的代碼也可以看出,在“A a1=new B()”語句中 A 類是父類、B 類是子類,相當於將子類轉換成父類,即隱式轉換。
如果需要將父類轉換成子類,則需要強制轉換,並且在強制轉換前需要先將所需的子類轉換成父類,示例代碼如下。
- A a2=new C();
- C c=(C) a2;
- c.Print();
在上面的實例中,a2 是父類對象,然后將其強制轉換成 C 類對象。
Object 類中的 ToString 方法能被類重寫,並返回所需的字符串,通常將其用到類中返回類中屬性的值。
在 Student 類中添加重寫的 ToString 方法,代碼如下。
- class Student
- {
- public string Major{ get; set;}
- public string Grade{ get; set;}
- public void Print()
- {
- Console.WriteLine("專業:"+ Major);
- Console.WriteLine("年級:"+ Grade);
- }
- public override string ToString()
- {
- return Major+","+Grade;
- }
- }
這樣,在調用 Student 類中的 ToString 方法時即可獲取專業和年級的值。
此外,除了 ToString 方法,在類中也可以重寫 Equals 方法、GetHashCode 方法。
