C#快速入門教程(15)—— 繼承

曹化宇發表於2018-10-06

繼承(inherit)是物件導向程式設計中一個非常重要的概念,其主要功能就是對已有程式碼的重複利用,以達到簡化開發、靈活擴充套件的目的。

類的繼承體系中,最基本的概念是繼承與被繼承的關係。我們先來看下面的程式碼。

using System;

namespace ConsoleTest
{
    public class Ca
    {
        public string Name { get; set; }
        public void DoWork()
        {
            Console.WriteLine("Ca.DoWork");
        }
    }
}

下面,我們定義一個Cb類,它繼承於Ca類,如下面的程式碼。

using System;

namespace ConsoleTest
{
    public class Cb : Ca
    {
    }
}

我們可以看到,Cb類的定義,“Cb : Ca”中的冒號(:)在這裡的含義就是繼承;接下來,我們在Program.cs檔案中測試Cb類的使用,如下面的程式碼。

static void Main(string[] args)
{
        Cb b = new Cb();
        b.DoWork();
}

程式碼執行結果如下圖所示。

enter image description here

從這個示例中,我們可以看到,Cb類並沒有定義任何成員,但其物件可以呼叫DoWork()方法,而這個方法就是從Ca類中繼承而來的。此時,Ca就是Cb的父類(parent class),也可以稱為超類(super class)或基類(base class);而Cb則是Ca的子類(sub class),它繼承於Ca類。

說到C#中的繼承,我們不提到Object類,它之所以特殊是因為在整個.NET Framework架構中,Object類是唯一一個沒有父類的類,而它也是其他所有型別的最終極父類,當一個類沒有指定父類時,它實際就繼承於Object類。也就是說,在.NET Framework架構中的所有型別都可以使用Object類中的非私有成員,比如GetType()方法、Equals()方法、ToString()方法等,當然,在子類中也可以修改父類成員的實現,從而更適合工作。

實際上,在這裡的Object類、Ca類和Cb的繼承關係就是Cb類繼承於Ca類、Ca類繼承於Object類,這樣,我們就可以在Cb類的物件中呼叫ToString()方法了,如下面的程式碼。

static void Main(string[] args)
{
        Cb b = new Cb();
        Console.WriteLine(b.ToString());
}

程式碼會顯示b物件的型別,如下圖所示。

enter image description here

實際應用中,如果一個類不允許被繼承,可以在定義是使用sealed關鍵將其“密封”,如下面的程式碼。

public sealed class Ca

在我們在定義Ca類時新增sealed關鍵字後就會出現Cb不能繼承Ca的提示,如下圖所示。

enter image description here

現在,我們已經瞭解了關於繼承的一些基本概念,接下來將討論在使用繼承關係時需要注意的地方。

建構函式

前面的課程,我們已經瞭解瞭如何靈活地建立建構函式,只是在繼承關係中,又多了一些選擇,下面的程式碼,首先在Ca類中建立兩個建構函式,如下面的程式碼。

using System;

namespace ConsoleTest
{
    public class Ca
    {
        // 建構函式
        public Ca(string sName)
        {
            Name = sName;
        }
        //
        public Ca() : this("noname") { }
        //
        public string Name { get; set; }
        public void DoWork()
        {
        vConsole.WriteLine("Ca.DoWork");
        }
    }
}

預設情況下,在Cb類中只會繼承Ca類的無引數建構函式,如下面的程式碼。

static void Main(string[] args)
{
        Cb b = new Cb();
        Console.WriteLine(b.Name);
}

程式碼會顯示noname。但是,如果我們使用Cb b = new Cb("Tom");程式碼建立Cb類的物件就會出現錯誤,如下圖所示。

enter image description here

下面的程式碼,我們通過Ca類中包含一個引數的建構函式來快速建立Cb類中的建構函式。

using System;

namespace ConsoleTest
{
    public class Cb : Ca
    {
        // 建構函式
        public Cb(string sName) : base(sName) { }
        public Cb() : this("Cb_noname") { }
    }
}

這裡,我們通過:符號和base(sName)呼叫了Cb父類(基類),也就是Ca類的單引數建構函式,再次執行Cb b = new Cb("Tom");就沒有問題了。請注意,當新增了任何有引數建構函式後,就不會自動生成無引數的建構函式,如果有需要再次新增,就像上面的程式碼一樣。

這裡,我們使用了base和this關鍵字,大家應注意,this表示當前例項,而base則用於呼叫父類(基類)中的非私有成員。

成員的訪問級別

訪問級別的概念,在前面的課程中已經討論過,具體到類的繼承關係中需要注意的是,定義在父類中的非私有成員,在其子類中可以使用base關鍵字訪問;這裡,父類的非私有成員在大多數情況下是指定義為受保護(protected)或公共(public)的成員。

抽象類及重寫

抽象類不能被例項化,也就是說,我們不能建立抽象類型別的物件。在C#中,定義抽象類時需要使用abstract關鍵字,如下面的程式碼,我們建立了Cx類,它被定義為抽象類。

using System;

namespace ConsoleTest
{
    public abstract class Cx
    {
        public abstract string Name { get; set; }
        public abstract void DoWork();
        //
        public void SayHello()
        {
            Console.WriteLine("Hello, {0}.", Name);
        }
    }
}

Cx類中共定義了三個成員,包括抽象屬性Name、抽象方法DoWork()和非抽象方法SayHello()。抽象類是不能建立例項的,所以,程式碼Cx x = new Cx();是不能執行的。

實際應用中,抽象類可以用於定義一系列型別的基本結構,其中可以包含一些共性操作;對於通用的操作可以定義為非抽象成員,如Cx類中的SayHello()方法,一些需要子類具體實現的成員則定義為抽象成員,如Cx類中的DoWork()方法等。需要注意的是,如果類中定義了一個抽象成員,那麼,類就必須定義為抽象類。

下面的程式碼,我們建立兩個Cx類的子類,分別是Cx1類和Cx2類。

//
public class Cx1 : Cx
{
    public override string Name { get; set; }
    public override void DoWork()
    {
        Console.WriteLine("Cx1.DoWork()");
    }
}
//
public class Cx2 : Cx
{
    public override string Name { get; set; }
    public override void DoWork()
    {
        Console.WriteLine("Cx2.DoWork()");
    }
}

在重寫抽象成員時,需要使用override關鍵字,如上述程式碼所示。下面的程式碼,我們使用Cx1和Cx2類。

static void Main(string[] args)
{
        Cx1 x1 = new Cx1() { Name = "Tom" };
        Cx2 x2 = new Cx2() { Name = "Jerry" };
        x1.SayHello();
        x1.DoWork();
        x2.SayHello();
        x2.DoWork();
}

程式碼執行結果如下圖所示。

enter image description here

虛擬成員及重寫

在類中定義為虛擬成員時需要使用virtual關鍵字,與抽象成員不同,虛擬成員可以有自己的實現,也可以在子類進行重寫,如下面的程式碼,我們定義Cy類。

using System;

namespace ConsoleTest
{
    public class Cy
    {
        public void DoWork1()
        {
            Console.WriteLine("Cy.DoWork1");
        }
        //
        public virtual void DoWork2()
        {
            Console.WriteLine("Cy.DoWork2");
        }
    }
}

請注意,Cy類中的DoWork2()方法使用了virtual關鍵字,而DoWork1()方法沒有使用virtual關鍵字。下面的程式碼,我們定義Cy類的子類Cy1類。

using System;

namespace ConsoleTest
{
    public class Cy1 : Cy
    {
        new public void DoWork1()
        {
            Console.WriteLine("Cy1.DoWork1");
        }
        //
        public override void DoWork2()
        {
            base.DoWork2();
            Console.WriteLine("Cy1.DoWork2");
        }
    }
}

這裡,我們定義的Cy1類作為Cy類的子類,其中包含同名的DoWork1()方法和DoWork2()方法,請注意它們的不同點。

Cy類中的DoWork1()方法定義時沒有使用virtual關鍵字,在其子類Cy1類中,如果需要重寫同名的方法,就應該在方法定義時使用new關鍵字,明確這是一個全新實現的同名成員。

Cy類中的DoWork2()方法定義時使用了virtual關鍵字,在其子類Cy1類中,則可以通過override關鍵字指明是在重寫父類中的方法。

下面的程式碼,我們測試Cy1類的使用。

static void Main(string[] args)
{
        Cy1 y1 = new Cy1();
        y1.DoWork1();
        y1.DoWork2();
}

程式碼執行結果如下圖所示。

enter image description here

CHY軟體小屋原創作品!

相關文章