天天看點

溯因推理:為什麼需要override和new兩種多态方案

該系列不是讨論關鍵字的用法或用處,是推測為什麼會有這樣的設計和規範,是大腦訓練工廠

有時候我們糾結迷茫,不知所措,其實最大的症結就在于不願意深想,不願意假設,不願意宏觀地去看待一切。雖然我們可能永遠都無法從微軟那裡獲得DotNet的源代碼來研讀,無法了解DotNet的内部機制,但并不代表我們不能自己對DotNet的内部機制進行假設和推斷。要成為一個名優秀的軟體工程師,學會使用推理的手法解決是一個非常有趣的過程,我們可以象江戶川柯南一樣在實際發生的事件中去推理真理。

什麼是推理?推理就是由一個或幾個已知的判斷(前提),推導出一個未知的結論的思維過程。推理是一種形式邏輯。是研究事物形式及其規律和一些簡單的邏輯方法的科學。其作用是從已知的知識得到未知的知識,特别是可以得到不可能通過感覺經驗掌握的未知知識。

中國有一句老話:知其然,須求其是以然。在本章我們會使用一些常用的推理手法通過C#的一些在文法的定義和編譯器的運作一系列的推理式學習,通過這個方式,讀者不但可以更透徹的了解C#知識,更能學習到一種新型的學習模式,在将來遇到各種困惑又沒有足夠可以查閱資料的情況下,能得到正确的結果。

溯因推理是指根據事物發展過程所造成的結果。推斷形成結果的一系列原因的整個邏輯思維過程,通俗的說就是從已知結果推斷其原因的一種思維方式,其目的是得到一個最佳的解釋,是一種典型的由果及因的方式。在邏輯結構上,它包括以下要素:

n 觀察現象陳述

n 導緻觀察現象的可能原因即猜測性假說

在C#的面向對象中,override是使用比較多的關鍵字。但你是否知道為什麼需要這兩個關鍵字,并且當子類類型轉為基類類型是,這兩個關鍵字定義的成員将有什麼不同嗎?

在推理override和new的機制之前,我們再次回顧下這兩個關鍵字的含義和用途。

n override:要擴充或修改繼承的方法、屬性、索引器或事件的抽象實作或虛實作,必須使用 override 修飾符(來自MSDN)。

n new:在用作修飾符時,new 關鍵字可以顯式隐藏從基類繼承的成員。隐藏繼承的成員時,該成員的派生版本将替換基類版本(來自MSDN)。

在中文的術語中我們将override稱為重寫而new稱為覆寫,并且MSDN在描述“何時使用 Override 和 New 關鍵字”時語焉不詳,僅給出了以下的使用建議和原則:

在 C# 中,派生類可以包含與基類方法同名的方法,其定義的原則是:預設情況下,C# 方法為非虛方法。如果某個方法被聲明為虛方法,則繼承該方法的任何類都可以實作它自己的版本。若要使方法成為虛方法,必須在基類的方法聲明中使用 virtual 修飾符。然後,派生類可以使用 override 關鍵字重寫基虛方法,或使用 new 關鍵字隐藏基類中的虛方法。如果 override 關鍵字和 new 關鍵字均未指定,編譯器将發出警告,并且派生類中的方法将隐藏基類中的方法。

為了解釋這段另人頭暈的文字,微軟在MSDN中還給出了一個更令人膛目結舌的案例。不過為了讓讀者能快速清晰的了解這段文字想描述的含義,我将其總結為三條口訣:

n 任何非抽象(abstract)的執行個體方法在C#中預設是非虛拟(virtual)的

n 子類要定義和父類中非抽象(abstract)或非虛拟(virtual)的同名方法時,必須使用new(覆寫)關鍵字

n 子類要定義和父類中抽象或虛拟的同名方法時,必須使用override(重寫)關鍵字

以下我們将實作一個繼承的模型,這個模型中我将使用到override和new關鍵字,類的對象圖如下

<a href="http://blog.51cto.com/attachment/201203/200332559.gif" target="_blank"></a>

圖Order類對象模型

以下我們将通過代碼實作這個模型,請讀者仔細觀察和跟随實作。

我們首先定義一個Order類,該類隻有兩個方法,其中PrintInfo方法是虛拟的,其目的是輸出一個簡單的資訊;PrintAuthor方法是非虛拟的,其目的是輸出目前Windows作業系統登入人員的名字。

public class Order

{

public virtual void PrintInfo()

System.Console.WriteLine("Order...");

}

public void PrintAuhor()

System.Console.WriteLine(System.Environment.UserName);

然後我們從Order類繼承一個派生類ShipOrder,該類使用override關鍵字重寫了PrintInfo方法

public class ShipOrder : Order

public override void PrintInfo()

System.Console.WriteLine("ShipOrder");

我們再次從Order類繼承一個派生類RoadOrder,該類使用override關鍵字重寫了PrintInfo方法,并且使用new關鍵字覆寫了Order中非虛拟的PrintAuhor方法

public class RoadOrder : Order

System.Console.WriteLine("RoadOrder");

public new void PrintAuhor()

System.Console.WriteLine(System.Environment.UserDomainName);

為展示這些類運作的結果,我們編寫以下的代碼

class Program

static void Main(string[] args)

new Order().PrintInfo();

new Order().PrintAuhor();

new ShipOrder().PrintInfo();

new ShipOrder().PrintAuhor();

new RoadOrder().PrintInfo();

new RoadOrder().PrintAuhor();

在檢視顯示結果前,筆者建議你自己先想像下運作的結果應該怎麼樣。下圖是運作的結果

<a href="http://blog.51cto.com/attachment/201203/200524540.gif" target="_blank"></a>

如果結果和你想像的一樣,那麼恭喜你,你對這兩個關鍵字的基本用法已經非常明确了。但是現在我們想像下,如果将子類ShipOrder和RoadOrder強制的轉為Order類型,并且再次調用PrintInfo和PrintAuthor方法會有什麼情況出現呢?換句話說:将子類轉換為父類的類型調用時,override和new方法是實作父類的行文還是自己自身的行為呢?

以下的運作代碼示範如何進行這個測試

System.Console.WriteLine("----------------");

((Order)new ShipOrder()).PrintInfo();

((Order)new ShipOrder()).PrintAuhor();

((Order)new RoadOrder()).PrintInfo();

((Order)new RoadOrder()).PrintAuhor();

運作的結果如圖,不知道和你想像的結果是否一緻?

<a href="http://blog.51cto.com/attachment/201203/200545746.gif" target="_blank"></a>

我們總結下運作的結果

當子類轉換為父類,且以父類的形式進行方法調用時:override總是指向自己的實作,而new總是指向父類的實作。

那麼為什麼會出現這樣的情況呢?請檢視下目前我們所了解的事實:

當父類的方法定義為抽象(abstract)或虛拟(virtual)時,子類使用override關鍵字

當父類沒有将方法定義為抽象(abstract)或虛拟(virtual)時,子類使用new關鍵字

現在我們将這個事實用表格的方式進行統計,統計的結果為:

<a href="http://blog.51cto.com/attachment/201203/200628668.jpg" target="_blank"></a>

從上面的表格我們可以清晰的得到如下的判斷

n 因為override時候父類的成員有可能是abstract,是以調用其父類方法有50%的失敗可能。

n 使用new關鍵字的時候,父類的成員必須是已經實作的,是以調用其父類決不可能會失敗。

是以,當子類類型轉換為基類類型時,為了確定最大的安全系數,使用override修飾的成員,都将指向子類自己的成員實作,而當成員是new定義的話,可以放心的調用父類類型中定義的成員了。

本文轉自shyleoking 51CTO部落格,原文連結:http://blog.51cto.com/shyleoking/803157

繼續閱讀