13.1 類和介面繼承 13.2 定義介面 C 用 interface 關鍵字定義介面.介面中可定義方法,事件,無參屬性和有參屬性(C 的索引器),但不能定義任何構造器方法,也不能定義任何實例欄位. 13.3 繼承介面 C 編譯器要求將實現介面的方法(簡稱為"介面方法")標記為public. CLR ...
13.1 類和介面繼承
13.2 定義介面
- C#用 interface 關鍵字定義介面.介面中可定義方法,事件,無參屬性和有參屬性(C#的索引器),但不能定義任何構造器方法,也不能定義任何實例欄位.
13.3 繼承介面
- C#編譯器要求將實現介面的方法(簡稱為"介面方法")標記為public.
CLR要求將介面方法標記為 virtual .不將方法顯式標記為 virtual ,編譯器會將它們標記為 virtual 和 sealed;這會阻止派生類重寫介面方法.將方法顯式標記為 virtual,編譯器就會將該方法標記為 virtual (並保持它的非密封狀態),使派生類能重寫它.
static class Program { static void Main() { /****************************第一個例子*****************************/ Base b = new Base(); //用b的類型來調用Dispose,顯示:"Base's Dispose" b.Dispose(); //用b的對象的類型來調用Dispose,顯示:"Base's Dispose" ((IDisposable)b).Dispose(); /****************************第二個例子*****************************/ Derived d = new Derived(); //用d的類型來調用Dispose,顯示:"Derived's Dispose" d.Dispose(); //用d的對象的類型來調用Dispose,顯示:"Derived's Dispose" ((IDisposable)d).Dispose(); /****************************第三個例子*****************************/ b = new Derived(); //用b的類型來調用Dispose,顯示:"Base's Dispose" b.Dispose(); //用b的對象的類型來調用Dispose,顯示:"Derived's Dispose" ((IDisposable)b).Dispose(); } } //這個類派生自Object,它實現了Idisposable internal class Base : IDisposable { //這個方法隱式密封,不能被重寫 public void Dispose() { Console.WriteLine("Base's Dispose"); } } //這個類派生自Base,它重新實現了IDisposable internal class Derived : Base, IDisposable { //這個方法不能重寫Base的Dispose, //'new'表明該方法重新實現了IDisposable的Dispose方法 new public void Dispose() { Console.WriteLine("Derived's Dispose"); //註意,下麵這行代碼展示瞭如何調用基類的實現(如果需要的話) //base.Dispose(); } }
總結:第三個例子第一條語句,是輸出"Base's Dispose",還是輸出"Derived's Dispose",取決於Base中的方法是否顯式標記 virtual ,並且Derived中用 override 標記為重寫. 換句話說,只有當基類允許重寫,同時子類願意重寫時,通過基類的變數調用子類的實例時,才會調用子類的實現.
13.4 關於調用介面方法的更多探討
和引用類型相似,值類型可實現零個或多個介面.但值類型的實例在轉換為介面類型時必須裝箱.
13.5 隱式和顯式介面方法實現(幕後發生的事情)
internal sealed class SimpleType : IDisposable
{
public void Dispose() { Console.WriteLine("public Dispose"); }
void IDisposable.Dispose() { Console.WriteLine("IDisposable Dispose"); }
}
- C#要求公共Dispose方法同時是IDisposable的Dispose方法的實現.
- 在C#中,將定義方法的那個介面的名稱作為方法名首碼(例如 IDisposable.Dispose),就會創建顯式介面方法實現(EIMI).c#中不允許在定義顯式介面方法時指定可訪問性(比如 public 或 private).但是編譯器生成方法的元數據時,可訪問性會自動設為private,防止其他代碼在使用類的實例時直接調用介面方法.只有通過介面類型的變數才能調用介面方法.
- EIMI方法不能標記為 virtual, 所以不能被重寫.
13.6 泛型介面
13.7 泛型和介面約束
- 可將泛型類型參數約束為多個介面.這樣,傳遞的參數的類型必須實現全部介面約束.
介面約束的第二個好處是傳遞值類型的實例時減少裝箱.
//向M方法傳遞Int32的實例時,不會發生裝箱. private static Int32 M<T>(T t) where T : IComparable,IConvertible {...} //如果M向下麵這樣聲明,傳遞Int32類型實例時會裝箱 private static Int32 M(IComparable t) {...}
C#編譯器為介面約束生成特殊IL指令,導致直接在值類型上調用介面方法而不裝箱.不用介面約束便沒有其他辦法讓C#編譯器生成這些IL指令.一個例外是如果值類型實現了一個介面方法,在值類型的實例上調用這個方法不會造成值類型的實例裝箱.
13.8 實現多個具有相同方法名和簽名的介面
要定義實現多個介面的類型,必須使用"顯式介面方法實現"來實現這個類型的成員.
public interface IWindow
{
object GetMenu();
}
public interface IRestaurant
{
object GetMenu();
}
//這個類型派生自System.Object,
//並實現了IWindow和IRestaurant介面
public sealed class MarioPizzeria : IWindow, IRestaurant
{
//這是IWindow的GetMenu方法的實現
object IWindow.GetMenu() { ... }
//這是IRestaurant的GetMenu方法的實現
object IRestaurant.GetMenu(){ ... }
//這個GetMenu方法是可選的,與介面無關
public object GetMenu() { ... }
}
代碼在使用MarioPizzeria對象時必須將其轉換為具體的介面才能調用所需的方法.
MarioPizzeria mp = new MarioPizzeria();
//這行代碼調用 MarioPizzeria 的公共 GetMenu 方法.
mp.GetMenu();
//以下代碼調用 MarioPizzeria 的 IWindow.GetMenu方法
IWindow window = mp;
window.GetMenu();
//以下代碼調用 MarioPizzeria 的 IRestaurant.GetMenu 方法
IRestaurant restaurant = mp;
restaurant.GetMenu();
13.9 用顯式介面方法實現來增強編譯時類型安全性
13.10 謹慎使用顯式介面方法實現
EIMI最主要的問題:
- 沒有文檔解釋類型具體如何實現一個EIMI方法,也沒有"智能感知"支持.
- 值類型的實例在轉換成介面時裝箱.
- EIMI不能由派生類型調用
13.11 設計:基類還是介面
可定義介面,同時提供實現該介面的基類.