在上一篇C#:類的繼承的最后一部分,我簡單演示了類的繼承中,通過在子類中添加父類沒有的成員實現了類成員的橫向擴展。
在本篇中,我們將演示如何對類成員進行縱向擴展,那就是通過重寫來實現。
重寫是什么?
- 重寫是針對函數成員而言的;
- 重寫是子類通過修改繼承自基類的函數成員而實現的一次版本更新;(版本更新--是為了方便理解而這樣叫的)
- 若要構成重寫,基類的函數成員 需要被 virtual修飾;該函數成員在子類中需要被 overrride修飾。
使用代碼認識一下什么是重寫:
class Shape
{
public double Area { get; set; }
public string Color { get; set; }
//使用virtual表明 該函數成員的功能希望在子類中被更新
public virtual void ShowInfo()
{
Console.WriteLine($"面積:{this.Area},顏色:{this.Color}");
}
}
class Square : Shape
{
//通過override,告訴父類,我升級了ShowInfo的功能
public override void ShowInfo()
{
Console.WriteLine("我有用四條邊,且對邊相等。。。。");
}
}
在Program類的Main函數中測試
class Program
{
static void Main(string[] args)
{
Square square = new Square();
square.ShowInfo();
Console.ReadLine();
}
}
/*輸出:我有用四條邊,且對邊相等。。。。*/
下面這種形式算重寫么?
class Square : Shape
{
public void ShowInfo()
{
Console.WriteLine("我有用四條邊,且對邊相等。。。。");
}
}
- 首先答案是:不算,因為子類的方法中沒有被override修飾;
- 那么這種形式算什么?我們看一下,編譯器給我們的提示:
這是編譯器只是給我們的警告:在子類中寫了一個和父類一樣的方法,就會隱藏掉繼承自父類的方法(我們還沒有介紹多態,如果介紹了多態以后,你就能明白這樣寫是十分不推薦的。)
警告中的后半段是說,我們可以使用override來重寫這個方法;當然這個我們剛剛才認識過,這里就不采用這個建議。
警告中的最后,告訴我們另一種合理的形式是,如果你真的是有意要隱藏掉父類的這個方法的話,你可以使用new關鍵修飾。
好吧,那么我們使用一下new關鍵字吧:可以看到警告消失了
- 當然最后,如果你沒有理睬編譯器的警告和提供的推薦,程序也能照常運行。編譯器也拿你沒辦法咯。
在引出多態的概念前,說一下 is a 的概念。
- C#中有一個操作符叫 is,由它組成的表達式的計算結果表示前者和后者是否類型兼容;
- 下面通過一個實例來使用一下 is 操作符:
static void Main(string[] args)
{
Square square = new Square();
var result = square is Object;//Object是所有類型的基類型,所以is表達式結果為true
if (result)
{
Object o = (Object)square;//(Object)變灰,表明square可以隱式轉換成Object類型引用
Console.WriteLine(o.GetType().BaseType.FullName);
}
Console.ReadLine();
}
/*輸出:ExtendsDemo.Shape*/
從示例中,我們可以得到下面結論,如果兩個類型之間存在繼承關系,那么is表達式結果為true,則子類引用可以隱式轉換成父類型引用
什么是多態?
- 父類型變量指向子類型對象;
- 父類型中的函數成員被子類重寫了;
- 當父類型引用調用函數成員時,調用的時子類中重寫了的函數成員;
- 以上就是對多態的描述,它隱含了:繼承、重寫。
1)最普通也是最常見的多態:
namespace ExtendsDemo
{
class Program
{
static void Main(string[] args)
{
Square square = new Square();
Shape shape = square;
shape.ShowInfo();
Console.ReadLine();
}
}
class Shape
{
public double Area { get; set; }
public string Color { get; set; }
//使用virtual表明 該函數成員的功能希望在子類中被更新
public virtual void ShowInfo()
{
Console.WriteLine($"面積:{this.Area},顏色:{this.Color}");
}
}
class Square : Shape
{
public override void ShowInfo()
{
Console.WriteLine("我有用四條邊,且對邊相等。。。。");
}
}
}
/*輸出結果:我有用四條邊,且對邊相等。。。。*/
是否很有意思,一個Shape類型的變量,從我們第一直覺上判斷,應該輸出的是Shape類型的ShowInfo函數中的輸出信息,但是結果卻並非如此。這就是多態特殊之處。
2) 一種破壞多態的形式
class Square : Shape
{
public new void ShowInfo()
{
Console.WriteLine("我有用四條邊,且對邊相等。。。。");
}
}
/*輸出:面積:0,顏色:*/
從輸出結果可以看出,使用new修飾后的ShowInfo函數,不會被父類型引用調用到,而是調用了父類中原有的函數成員;本來我們將代碼寫成父類型變量=子類型對象的形式,就是為了使用多態,這樣一來不就是破壞了多態性么?
3) 通過多層繼承鏈,理解"父類型引用永遠調用的是繼承鏈中最新版本的重寫函數"這句話。
namespace ExtendsDemo
{
class Program
{
static void Main(string[] args)
{
var zhengfangxing = new ZhengFangXing();
Shape shape = zhengfangxing;
shape.ShowInfo();
Console.ReadLine();
}
}
class Shape
{
public double Area { get; set; }
public string Color { get; set; }
//使用virtual表明 該函數成員的功能希望在子類中被更新
public virtual void ShowInfo()
{
Console.WriteLine($"面積:{this.Area},顏色:{this.Color}");
}
}
class Square : Shape
{
public override void ShowInfo()
{
Console.WriteLine("我有用四條邊,且對邊相等。。。。");
}
}
class ZhengFangXing:Square
{
public override void ShowInfo()
{
Console.WriteLine("我是特殊的長方形。。。我叫正方形");
}
}
}
/*輸出:我是特殊的長方形。。。我叫正方形*/
從Shape到Squre再到ZhengFanxing總共經歷了兩次重寫(我稱它叫版本升級),那么Shape類型的變量訪問的到繼承鏈上的最新版本,就是ZhengFangXing的ShowInfo()
4) 如果繼承鏈某一層使用了new,你還能知道父類型引用調用哪個類的成員么?
namespace ExtendsDemo
{
class Program
{
static void Main(string[] args)
{
var zhengfangxing = new ZhengFangXing();
Shape shape = zhengfangxing;
shape.ShowInfo();
Console.ReadLine();
}
}
class Shape
{
public double Area { get; set; }
public string Color { get; set; }
//使用virtual表明 該函數成員的功能希望在子類中被更新
public virtual void ShowInfo()
{
Console.WriteLine($"面積:{this.Area},顏色:{this.Color}");
}
}
class Square : Shape
{
public new virtual void ShowInfo()
{
Console.WriteLine("我有用四條邊,且對邊相等。。。。");
}
}
class ZhengFangXing:Square
{
public override void ShowInfo()
{
Console.WriteLine("我是特殊的長方形。。。我叫正方形");
}
}
}
/*輸出:面積:0,顏色:*/
不知道你是否猜對?沒關系,我們分析一下原因:在例子2中我們說過,new並不是一種重寫形式,我更願意把它當作是一種新成員(橫向擴展),它不會帶來版本更新;從基類出發順着繼承鏈,向下找到最新版本的重寫;在new出現的那一層,基類發現則並不是一種重寫(即沒有最新版本),所以基類型引用就調用了自己的函數成員。
父類型引用可以引用不同的子類實例,這是一種多態性的體現;父類型中的方法被子類重寫,而擁有了各種各樣的功能,這是多態的一種行為上的體現;多態性大大提升了程序的擴展性:
namespace ExtendsDemo
{
class Program
{
static void Main(string[] args)
{
Shape square = new Square();
Shape circle = new Circle();
Shape trangle = new Trangle();
square.ShowInfo();
circle.ShowInfo();
trangle.ShowInfo();
Console.ReadLine();
}
}
class Shape
{
public double Area { get; set; }
public string Color { get; set; }
//使用virtual表明 該函數成員的功能希望在子類中被更新
public virtual void ShowInfo()
{
//既然每個子類都要升級該函數的功能,那就干脆不寫任何功能代碼了
}
}
class Square : Shape
{
public override void ShowInfo()
{
Console.WriteLine("我有用四條邊,且對邊相等。。。。");
}
}
class Circle : Shape
{
public override void ShowInfo()
{
Console.WriteLine("我圓形,我特圓....");
}
}
class Trangle : Shape
{
public override void ShowInfo()
{
Console.WriteLine("我是三角形,我具有穩定性....");
}
}
}
/*輸出:
我有用四條邊,且對邊相等。。。。
我圓形,我特圓....
我是三角形,我具有穩定性....
*/
上面代碼演示了,多態的使用;需要注意的是,在基類中ShowInfo方法中的一段注釋;這段注釋是為了引出抽象類:因為聲明了一個virtual函數,而這個函數里面卻什么也沒做,這看起來是不是很奇怪?下篇文章我將記錄專為做基類而生的"抽象類",它就能很好地解決目前我們遇到的"沒有功能代碼的空函數"的問題。
以上是對基於繼承的重寫和多態的總結,記錄下來以便以后查閱。