1. 程式人生 > 其它 >visual code 編譯c#_C# 學習之路(十一)

visual code 編譯c#_C# 學習之路(十一)

技術標籤:visual code 編譯c#

繼承與介面(下)

本系列文章主要意在總結筆者在學習過程中學到的有關 C# 特性的知識,分享 C# 中較為重要和突出的部分和有助養成良好程式設計習慣的提示。

並非旨在系統介紹 C#。


aa2d871a26a074a6d66c0d73a540c0ab.png8月考試。

本篇文章是以繼承與介面為主題的文章。希望在鞏固總結筆者有關知識的基礎上,能為讀者提供一些額外的小知識。本文以《Visual C#從入門到精通(第九版)》第 12-13 章 為藍本,進行了一定程度的改編,增加了一些筆者的理解和示例,方便讀者理解和閱讀。


目錄:

  • C# 學習之路(十)勘誤

  • 背景

  • 定義,(隱式)實現介面

  • 顯式實現介面

  • 通過介面引用類

  • 使用介面的注意事項


C# 學習之路(十)勘誤:

C# 學習之路(十)中的如下例子存在錯誤:

staticclassUtil{    public static int PlusPlusPlus(this int i, int j, int k)     {        return (i + j + k);    }}int x = 591;x.PlusPlusPlus(10, 20); // 只需傳入後兩個引數Console.WriteLine(x); // 輸出 621

PlusPlusPlus方法需要修改 i 引數的實參,而由我們之前在 C# 學習之路(六)(七)中對值與引用的討論得知,PlusPlusPlus 方法存在如下兩個無法修改實參 x 的問題:其一,PlusPlusPlus 方法只返回了表示式(i + j + k) 的值,而未修改哪怕一個引數。其二,不使用 ref 或 out 關鍵字修飾引數,意味著就算在方法內修改引數,仍然無法從原變數處體現出變化,因為這種修改作用於原變數的拷貝而非其本身。具體情況可以參見 C# 學習之路(六)(七)。

在不修改PlusPlusPlus方法的前提下,應如下呼叫該方法:

intx=591;Console.WriteLine(x.PlusPlusPlus(10, 20)); // 輸出 621

出現這種低階錯誤,筆者當深刻反思。


背景:

從類繼承是很強大的機制,但繼承真正的強大之處是能從介面繼承。介面不含任何程式碼或資料;它只是規定從介面繼承的類必須提供哪些方法或屬性(屬性將在後續文章中討論)。使用介面,方法的名稱/簽名可以完全獨立於方法的實現。

介面相當於一份協議。繼承了介面的類必然實現了介面要求實現的所有方法和屬性。這樣就能保證我們在使用繼承了某個介面的類時,能清晰的認識到,這個類實現了哪些方法和屬性。

使用介面,可以真正的將“what”(有什麼)和“how”(怎麼做)區分開。介面指定有什麼,也就是方法的名稱、返回型別和引數,以及屬性的名稱、訪問器。至於具體怎麼做,或者說介面內容的實現,則不是介面所關心的,應當由繼承介面的類來決定怎麼做。簡言之,介面描述了類提供的功能,但不描述如何實現


定義,(隱式)實現介面:

定義介面和定義類相似,只不是使用的是interface 關鍵字而不是 class。若想在介面中宣告方法,只需按照和在類/結構中定義方法一樣的步驟即可。需要注意的是,在介面中宣告方法時無需也不應該實現方法,它們只是宣告在介面中。介面中方法如何實現取決於繼承介面的類。因此,我們需要使用分號結束方法語句,無需提供方法主體,示例如下:

interfaceIComparable//用interface關鍵字宣告介面{intCompareTo(objectobj);//只宣告方法,但不實現。用分號代替方法主體。}

介面不含任何資料,不可在介面中新增任何欄位。介面名稱建議以大寫字母 I 開頭。可以注意到,我們並沒有對方法做訪問限制,訪問限制符也應當由實現介面的類提供。

而正如前面所說,介面的實現要放在繼承了介面的類中,且該類需要實現介面指定的所有內容。先宣告一個ILanBound 介面:

interfaceILandBound//定義陸上介面宣告一個返回陸地動物腿的個數的方法{intNumberOfLegs();}

然後我們用 Horse 類繼承ILandBound 介面:

classHorse:ILandBound // 繼承介面{//TODO:publicintNumberOfLegs()//實現介面中的方法,只能用public修飾可訪問性{    return 4;}}

在類中實現介面時有如下幾個規則需要遵守:

  • 方法名和返回型別必須與介面中一致。

  • 方法的所有引數與介面中一致,包括引數字首(如 ref 和 out)。

  • 方法必須具有 public 級別的可訪問性。

我們再來看一個例子:

class Mammal{    // TODO:}interface ILandBound{    // TODO:}interfaceIDebug{//TODO:}class Horse : Mammal, ILandBound, IDebug // 繼承一個類和多個介面{//TODO:}

在上例中,Horse 類繼承了一個基類和兩個介面。在 C# 中,如果實現介面的類派生自其他類,則需將其基類置於繼承列表的最前方。類可以繼承多個介面,但只能繼承一個基類。

和類一樣,介面也可以從另一個介面繼承。事實上,應該將這種繼承稱為介面擴充套件。如果介面 InterfaceA 繼承自介面 InterfaceB,那麼實現介面 InterfaceA 的類需要實現介面 InterfaceA 和 InterfaceB 規定的所有內容。


顯式實現介面:

前面的例子描述瞭如何在類中隱式實現介面。但這種方式存在一定侷限性。試想,如果介面 InterfaceA 和介面 InterfaceB 都聲明瞭 NumberOfLegs 無參方法。但它們的目的不同,InterfaceA 的NumberOfLegs 方法意在返回動物的腿(legs)的個數,InterfaceB 的NumberOfLegs 意在返回動物經過了幾站(legs)。(在英文中,leg 可以表示路程的一部分,比如“the last leg of a trip”代表此行最後一站,因此此處兩個介面中的 NumberOfLegs 方法名所蘊含的意思不同)

現在 Horse 類需要實現InterfaceA和InterfaceB 介面,也就意味著它需要實現兩個NumberOfLegs 方法。但如果隱式實現介面,那麼編譯器如何知道哪個NumberOfLegs方法是InterfaceA 宣告的,而哪個NumberOfLegs 又是InterfaceB宣告的呢?

interface InterfaceA{intNumberOfLegs();//返回動物腿的個數}interfaceInterfaceB{    int NumberOfLegs(); // 返回動物經過了幾站}class Horse : InterfaceA, InterfaceB{publicintNumberOfLegs(){return4;}//編譯器如何知道這是哪個介面的實現?!}

這樣的程式碼是合法的!在編譯器看來,雖然 Horse 類只實現了一個 NumberOfLegs 方法,但這一個方法實現了兩個介面

為了解決該問題,並區分哪個方法實現哪個介面,應當顯式實現介面。為此,要指出方法從屬於哪個介面:

interface InterfaceA{    int NumberOfLegs(); // 返回動物腿的個數}interface InterfaceB{    int NumberOfLegs(); // 返回動物經過了幾站}class Horse : InterfaceA, InterfaceB{intInterfaceA.NumberOfLegs()    {        return 4;    }//實現A介面,返回4 條腿    int InterfaceB.NumberOfLegs()    {        return 3;    }    // 實現 B 介面,返回 3 站}

我們可以發現,顯式實現介面時,無需使用訪問限制符修飾方法。這樣就意味著,這兩個NumberOfLegs 方法都是私有方法!(不新增訪問限制符時,預設宣告欄位或方法為私有)為何要這麼設計?這是因為,如果能直接在外部呼叫這兩個NumberOfLegs 方法,那麼此時,又如何得知呼叫的是哪個方法呢?

Horsehorse=newHorse();int legs = horse.NumberOfLegs();// 無法編譯!//如果能直接呼叫 NumberOfLegs 方法,編譯器如何得知呼叫的是哪個方法?

此時我們需要通過介面引用類的方式來呼叫 NumberOfLegs 方法,這種方式能區分呼叫的是哪個方法,同時還具有其他優勢。


通過介面引用類:

和基類變數能引用派生類物件一樣,介面變數也能引用實現了該介面的類。

Horsehorse=newHorse();InterfaceAiMyA=horse;//合法InterfaceBiMyB=horse;//合法

而為了呼叫InterfaceA 介面和InterfaceB介面中不同的NumberOfLegs 方法,我們需要使用引用了 Horse 物件的介面變數來呼叫相應方法。

Horse horse = new Horse();InterfaceAiMyA=horse;// 合法引用InterfaceBiMyB=horse;// 合法引用intlegsA=iMyA.NumberOfLegs();//呼叫實現A介面的NumberOfLegsintlegsB=iMyB.NumberOfLegs();//呼叫實現B介面的NumberOfLegs

編譯器會根據介面變數的型別來決定呼叫類中的哪個 NumberOfLegs 方法。我們需要建立介面變數並引用類才能通過該介面變數呼叫類中實現的介面方法。為了讓這句話能被看懂,可以將其拆分如下:

  • 定義介面變數並引用類

  • 通過該變數訪問類中實現的介面方法,在上例中就是NumberOfLegs 方法

當介面方法被顯式實現時,我們就需要按照如上方式呼叫介面方法。

通過介面引用類是一項相當有用的技術。

intFind(InterfaceAmyA){    // TODO:}

在上面這個 Find 方法中,我們可以獲取任意實現了InterfaceA 的實參。比如,Horse 類和 Person 類都實現了InterfaceA介面,那麼任意Horse 類或 Person 類變數都可作為實參傳進Find 方法。這種傳參方式即擴充套件了傳入引數的型別——實現介面的所有型別,又防止未實現介面的型別傳入。相較將形參設定為 object 型別,是不是有很大的優勢呢?

可用 is 操作符驗證物件是不是實現了介面的一個類的例項。

Horsehorse = new Horse();if(horseisInterfaceA) // is 操作符返回布林值{    InterfaceA iMyA = horse;}

使用介面的注意事項:

  • 欄位本質上是類或結構的實現細節。介面中不允許定義任何欄位。

  • 構造器也是類或結構的實現細節。介面中不允許定義任何構造器。

  • 介面中不允許定義析構器。

  • 不能為介面中的方法指定訪問限制符。介面所有方法都隱式成為公共方法。

  • 介面不能從類繼承,但可從介面繼承。

本文中C#的有關知識整理歸納自《VisualC#從入門到精通(第九版)》清華大學出版社JohnSharp著周靖譯ISBN978-7-302-51624-8十分推薦想學習C#的朋友購入學習

筆者水平有限,如有錯誤,歡迎給公眾號留言反饋(文章目前不支援留言)2cf527821f2db6b7b6cc05e26bee711e.png