CLR以及所有托管語言都不支持多繼承,通過接口模擬實現
14.1 類和接口的實現
接口定義:為一組方法簽名指定一個名稱的方式。
類實現接口,就一定要提供接口所有方法的實現。
即使抽象類,也要全部實現,但是,它可以把接口方法聲明為abstract的,從而把這個接口方法留給 派生類去實現,如下:
public interface ITest { void Test(); } public abstract class AbstractClass : ITest { public abstract void Test(); } public class ConcreateClass : AbstractClass { public override void Test() { //Coding implementation; } }
14.2 定義接口
接口中可以有方法/事件/屬性,因為後者本質上也是 方法
接口中不可以有靜態成員(包括常量/枚舉)
接口之間可以"繼承",可以認為是包含另一種接口的約定,並不是真正意義的繼承
接口下所有成員,默認為public,不用聲明
14.3 實現接口
實現的接口方法,一定要標記為public,此時在IL中為virtual和sealed,即不允許子類重寫該方法( 這時new也不管用了)。
要顯示將調用的接口方法標記為virtual,則可以在子類中重寫該方法
//如果顯示標記為sealed,那麼就更不可以重寫了
14.4 調用接口方法
在運行時,可以將一個變量從一種接口類型轉型為另一種接口類型,只要該對象的類型實現了這兩種 接口,如下:
//String實現了Icloneable, IEnumerable String s = "Jax"; ICloneable cloneable = s; IEnumerable enumable = (IEnumerable)cloneable;
注:cloneable只可使用ICloneable的接口方法,不可以使用String的方法;enumable變量雖由 ICloneable轉型而來,但也不能使用ICloneable接口方法。
值類型也可以實現接口,但是在轉成接口類型前要先裝箱——接口變量必須是指向堆上的一個對象的 引用。
//Int32實現了IFormattable接口 Int32 i = 0; //i在轉成接口類型前要先裝箱 IFormattable formattable = (IFormattable)i;
14.5 接口方法的隱式/顯示實現
接口方法一般都是隱式實現的,可訪問性一定要聲明為public。
EIMI:顯示接口方法實現,用定義方法的那個接口的名稱來作為方法名稱的前綴。
不屬於類型對象的一部分,只是將一個接口連接到類型上,同時避免了暴露行為和方法
不能指定可訪問性public/private——在IL中標記為為private,只有通過接口變量才能訪問該方法, 以防止類型對象直接訪問。
不能標記為virtual,不能被重寫。
public interface ITest { void Test(); } public class TestClass : ITest { public void Test() { Console.WriteLine("1"); } void ITest.Test() { Console.WriteLine("2"); } } TestClass t = new TestClass(); t.Test(); //輸出1 ITest it = (ITest)t; it.Test(); //輸出2
14.6 泛型接口
3個好處
1.編譯時的類型安全性
Int32 x = 1; String s = "1"; IComparable c = x; c.CompareTo(x); c.CompareTo(s); //運行期錯誤,因為Object是不 安全類型 IComparable<Int32> cc = x; //強類型,所以直接受整型x,不接受 字符串s,否則編譯期報錯
2.操作值類型時減少裝箱
上個例子將x傳到Compare方法要裝箱,使用泛型不用裝箱,按值傳遞。
非泛型現在仍存在於FCL,是為了向後兼容。
3.同一個類可以實現同一個泛型接口若干次,只要使用不同類型參數
public sealed class Number : IComparable<Int32>, IComparable<String> { public int CompareTo(int other) { } public int CompareTo(string other) { } }
可以把IComparable<Int32>和IComparable<String>看作兩個不同的接口,就好理解了。
14.7 泛型接口的參數約束
2個好處:
1.可以將類型參數約束為多個接口,從而使傳入的參數類型必須實現所有接口約束
2.減少裝箱
參數約束,會生成特定的IL語言,使得直接在值類型上調用接口方法,而不用裝箱。
14.9 用EIMI改進編譯時類型安全
使用EIMI技術,處理非泛型接口,保證類型安全
struct SomeValueType : IComparable { private Int32 m_x; public SomeValueType(Int32 x) { m_x = x; } //這是一個類型安全的方法 public Int32 CompareTo(SomeValueType other) { return m_x - other.m_x; } //這是一個類型不安全的方法,但是一定要有,才能保證編譯通過,因為參數不同,可 以認為是重載,而且這個方法才是接口方法的實現 Int32 IComparable.CompareTo(Object other) { return CompareTo((SomeValueType)other); } }
調用的時候要注意:
SomeValueType v = new SomeValueType(); Object o = new Object(); Int32 n = v.CompareTo(v); //類對象v的Comapre方法,保證類型安全 IComparable c = v; n = c.CompareTo(v); n = c.CompareTo(o); //接口對象c的Comapre方法,不能保 證類型安全,所以不要使用接口對象
14.10 EIMI的缺點
3個缺點:
1.沒有說明具體如何實現一個EIMI方法
2.值類型實例在轉換為接口類型時,會被裝箱
3.EIMI方法不能被派生類型繼承
14.11 接口與類繼承
類繼承: 表示 IS-A。易於使用,不必提供所有實現;可以override和new重寫;易於在基類中添加成 員,而不需改動子類
接口: 表示 CAN-DO。以上類繼承的優點一概沒有。
值類型繼承自System.ValueType,只能使用接口
FCL的集合基於接口,因為各種集合間極少共享的代碼。