關於介面繼承與覆蓋基底介面成員的討論

來源:互聯網
上載者:User
發這篇文章的起因源於一位朋友的發問:http://community.csdn.net/Expert/TopicView3.asp?id=5118454

該帖的回複中我沒能給出令人信服的解答,在他的追問下,我被迫重新思考了他的提問,感覺我原先理解錯了問題的意思,回答的似是而非,因此有必要在這裡重新說明一下。

該朋友的問題如下:
interface IInterfaceA
{
void DoSomething();
}

interface IInterfaceB:IInterfaceA
{
new void DoSomething();//隱藏基底介面的成員
}

//這兩個介面有什麼不一樣嗎?好像IInterfaceB隱藏IInterfaceA的成員DoSomething也沒什麼作用啊.
//能舉個簡單的例子(代碼)幫我理解一下嗎。謝謝。

他問的其實是這種應用的意義,換句話說,有必要一定這樣做嗎?在哪裡才會需要這種應用?
如果沒有過實際的應用確實很難臆想有這種需求的情境,於是我在VS的隨機文檔中找到了這樣一個例子。它原本是用來說明隱式介面實現與明確介面實作的區別的。
-----------------------------------------------------------------------------------------
顯 式介面實現還允許程式員實現具有相同成員名稱的兩個介面,並為每個介面成員各提供一個實現。本樣本同時以公制單位和英制單位顯示框的尺寸。Box類實現 IEnglishDimensions 和 IMetricDimensions 兩個介面,它們表示不同的度量系統。兩個介面有相同的成員名 Length 和 Width。

樣本
// Declare the English units interface:
interface IEnglishDimensions
{
float Length();
float Width();
}

// Declare the metric units interface:
interface IMetricDimensions
{
float Length();
float Width();
}

// Declare the Box class that implements the two interfaces:
// IEnglishDimensions and IMetricDimensions:
class Box : IEnglishDimensions, IMetricDimensions
{
float lengthInches;
float widthInches;

public Box(float length, float width)
{
lengthInches = length;
widthInches = width;
}

// Explicitly implement the members of IEnglishDimensions:
float IEnglishDimensions.Length()
{
return lengthInches;
}

float IEnglishDimensions.Width()
{
return widthInches;
}

// Explicitly implement the members of IMetricDimensions:
float IMetricDimensions.Length()
{
return lengthInches * 2.54f;
}

float IMetricDimensions.Width()
{
return widthInches * 2.54f;
}

static void Main()
{
// Declare a class instance box1:
Box box1 = new Box(30.0f, 20.0f);

// Declare an instance of the English units interface:
IEnglishDimensions eDimensions = (IEnglishDimensions)box1;

// Declare an instance of the metric units interface:
IMetricDimensions mDimensions = (IMetricDimensions)box1;

// Print dimensions in English units:
System.Console.WriteLine("Length(in): {0}", eDimensions.Length());
System.Console.WriteLine("Width (in): {0}", eDimensions.Width());

// Print dimensions in metric units:
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}
}

輸出

Length(in): 30
Width (in): 20
Length(cm): 76.2
Width (cm): 50.8

可靠編程
如果希望預設度量採用英制單位,請正常實現 Length 和 Width 這兩個方法,並從 IMetricDimensions 介面顯式實現 Length 和 Width 方法:

// Normal implementation:
public float Length()
{
return lengthInches;
}
public float Width()
{
return widthInches;
}

// Explicit implementation:
float IMetricDimensions.Length()
{
return lengthInches * 2.54f;
}
float IMetricDimensions.Width()
{
return widthInches * 2.54f;
}

這種情況下,可以從類執行個體訪問英制單位,而從介面執行個體訪問公制單位:

public static void Test()
{
Box box1 = new Box(30.0f, 20.0f);
IMetricDimensions mDimensions = (IMetricDimensions)box1;

System.Console.WriteLine("Length(in): {0}", box1.Length());
System.Console.WriteLine("Width (in): {0}", box1.Width());
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}
-----------------------------------------------------------------------------------------
我對其中的介面定義進行了改寫:
interface IEnglishDimensions
{
float Length();
float Width();
}
interface IMetricDimensions : IEnglishDimensions
{
new float Length();
new float Width();
}

在那位朋友提問的例子裡,子介面中是否用new覆蓋(注意我這裡用詞"覆蓋",而非"隱藏"。)基底介面的成員得到的效果都是一樣的。但在我修改了介面定義後的MS樣本裡,new關鍵字就顯示出了它的意義。如果我這樣修改介面定義:
interface IEnglishDimensions
{
float Length();
float Width();
}
interface IMetricDimensions : IEnglishDimensions
{
//new float Length();
//new float Width();
}
那麼這個樣本是無法通過編譯的,它會提示你

顯式介面聲明中的“IMetricDimensions.Length”不是介面成員
顯式介面聲明中的“IMetricDimensions.Width”不是介面成員

可通過編譯的做法是在Class Box中顯示實現IEnglishDimensions,而隱式實現IMetricDimensions。但那樣我們會發現列印出的結果是這樣的:

輸出

Length(in): 30
Width (in): 20
Length(cm): 30
Width (cm): 20

現在介面繼承又同時提供new關鍵字覆蓋基底介面成員的好處就顯而易見了。

我保證了IMetricDimensions的介面定義與IMetricDimensions的嚴格一致。我的Box同時支援了兩種計量制度,它們在成員定義上是嚴格相同的,只是進位不同。
我 也可以像MS 原例中那樣書寫,完全定義成兩個不相關的介面,但如果我要為IEnglishDimensions增加任何成員的時候,我就必須不得同時去修改 IMetricDimensions的定義。如果我忘記了,或者在此出現了什麼疏露,那麼我的Box在應用IMetricDimensions這種公制計 量制度時就不能支援新增的成員。

以上乃 [緣木漁人 個人之見] 。

感興趣的朋友也可以看看這篇文章http://www.miisoft.com/article/2/28/2006/200607258088.html,它是談論new關鍵字在C#的多態中的應用的,講的是基類與介面混合繼承的情況。

感謝11Z大哥在我思考這個問題中給以啟發並提供參考文章。
也感謝sh_city (高)朋友的發問,讓我注意並深刻思考了這個問題。

----------------------------------------------------------------------------------------
另外,和11Z大哥討論這個問題的時候,11Z老大認為C#做此設計的初衷是為了方便版本的管理與控制。昨天我還不甚理解,今天也在MSDN的文章中找到了證據。下面將引用原文。

原文是討論類繼承的情況的,但其實轉換到介面中來也是一樣。
---------------------------------------------------------------------------------------
C# 語言經過專門設計,以便不同庫中的基類與衍生類別之間的版本控制可以不斷向前發展,同時保持向後相容。這具有多方面的意義。例如,這意味著在基類中引入與派 生類中的某個成員具有相同名稱的新成員在 C# 中是完全支援的,不會導致意外行為。它還意味著類必須顯式聲明某方法是要重寫一個繼承方法,還是一個僅隱藏具有類似名稱的繼承方法的新方法。

C# 允許衍生類別包含與基類方法名稱相同的方法。

基類方法必須定義為 virtual。

如果衍生類別中的方法前面沒有 new 或 override 關鍵字,則編譯器將發出警告,該方法將有如存在 new 關鍵字一樣執行操作。

如果衍生類別中的方法前面帶有 new 關鍵字,則該方法被定義為獨立於基類中的方法。

如果衍生類別中的方法前面帶有 override 關鍵字,則衍生類別的對象將調用該方法,而不調用基類方法。

可以從衍生類別中使用 base 關鍵字調用基類方法。

override、virtual 和 new 關鍵字還可以用於屬性、索引器和事件中。

默 認情況下,C# 方法不是虛方法 -- 如果將一種方法聲明為虛方法,則繼承該方法的任何類都可以實現其自己的版本。若要使方法成為虛方法,必須在基類的方法聲明中使用 virtual 修飾符。然後,衍生類別可以使用 override 關鍵字重寫基虛方法,或使用 new 關鍵字隱藏基類中的虛方法。如果 override 關鍵字和 new 關鍵字均未指定,編譯器將發出警告,並且衍生類別中的方法將隱藏基類中的方法。有關更多資訊,請參見編譯器警告 CS0108。

為了在實踐中示範上述情況,我們暫時假定公司 A 建立了一個名為 GraphicsClass 的類,您的程式使用該類。GraphicsClass 類似如下:

C# 複製代碼
class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
}

您的公司使用此類,並且您在添加新方法時將其用來派生自己的類:

C# 複製代碼
class YourDerivedGraphicsClass : GraphicsClass
{
public void DrawRectangle() { }
}

您在應用程式的使用過程中沒有遇到任何問題,直到公司 A 發布了 GraphicsClass 的新版本,該新版本類似如下:

C# 複製代碼
class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
public virtual void DrawRectangle() { }
}

現 在,GraphicsClass 的新版本中包含了一個稱為 DrawRectangle 的方法。最初,一切正常。新版本仍與舊版本二進位相容 -- 即使在電腦系統中安裝新類,部署的所有軟體仍將繼續工作。在您的衍生類別中,對方法 DrawRectangle 的任何現有調用將繼續引用您的版本。

但是,一旦使用 GraphicsClass 的新版本重新編譯應用程式,您將收到來自編譯器的警告。有關更多資訊,請參見編譯器警告 CS0108。

此警告提示您需要考慮您的 DrawRectangle 方法在應用程式中的工作方式。

如果想用您的方法重寫新的基類方法,請使用 override 關鍵字,如下所示:

C# 複製代碼
class YourDerivedGraphicsClass : GraphicsClass
{
public override void DrawRectangle() { }
}

override 關鍵字可確保派生自 YourDerivedGraphicsClass 的任何對象都將使用 DrawRectangle 的衍生類別版本。派生自 YourDerivedGraphicsClass 的對象仍可以使用 base 關鍵字訪問 DrawRectangle 的基類版本,如下所示:

C# 複製代碼
base.DrawRectangle();

如 果不想用您的方法重寫新的基類方法,則應注意下面的事項。為避免在兩種方法之間引起混淆,可以重新命名您的方法。重新命名方法可能很耗時且容易出錯,而且在某 些情況下並不實用。但是,如果您的項目相對較小,則可以使用 Visual Studio 的重構選項來重新命名方法。有關更多資訊,請參見重構類和類型。

或者,也可以通過在衍生類別定義中使用關鍵字 new 來防止出現該警告,如下所示:

C# 複製代碼
class YourDerivedGraphicsClass : GraphicsClass
{
public new void DrawRectangle() { }
}

使用 new 關鍵字告訴編譯器您的定義將隱藏基類中包含的定義。這是預設行為。

重寫和方法選擇
當在類中指定方法時,如果有多個方法與調用相容(例如,存在兩種同名的方法,並且其參數與傳遞的參數相容),則 C# 編譯器將選擇最佳方法進行調用。下面的方法將是相容的:

C# 複製代碼
public class Derived : Base
{
public override void DoWork(int param) { }
public void DoWork(double param) { }
}

在 Derived 的一個執行個體中調用 DoWork 時,C# 編譯器將首先嘗試使該調用與最初在 Derived 上聲明的 DoWork 版本相容。重寫方法不被視為是在類上進行聲明的,而是在基類上聲明的方法的新實現。僅當 C# 編譯器無法將方法調用與 Derived 上的原始方法匹配時,它才嘗試將該調用與具有相同名稱和相容參數的重寫方法匹配。例如:

C# 複製代碼
int val = 5;
Derived d = new Derived();
d.DoWork(val); // Calls DoWork(double).

由 於變數 val 可以隱式轉換為 double 類型,因此 C# 編譯器將調用 DoWork(double),而不是 DoWork(int)。有兩種方法可以避免此情況。首先,避免將新方法聲明為與虛方法同名。其次,可以通過將 Derived 的執行個體強制轉換為 Base 來使 C# 編譯器搜尋基類方法列表,從而使其調用虛方法。由於是虛方法,因此將調用 Derived 上的 DoWork(int) 的實現。例如:

C# 複製代碼
((Base)d).DoWork(val); // Calls DoWork(int) on Derived.

qltouming(緣木漁人)
2006-11-02

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.