C#:類的繼承--重寫和多態


在上一篇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函數,而這個函數里面卻什么也沒做,這看起來是不是很奇怪?下篇文章我將記錄專為做基類而生的"抽象類",它就能很好地解決目前我們遇到的"沒有功能代碼的空函數"的問題。

以上是對基於繼承的重寫和多態的總結,記錄下來以便以后查閱。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM