在C#8.0中,針對接口引入了一項新特性,就是可以指定默認實現,方便對已有實現進行擴展,也對面向Android和Swift的Api進行互操作提供了可能性。下面我們來看看該特性的的概念、規則與示例代碼。
一、什么是默認實現
顧名思義,默認實現就是接口中的成員可以進行實現,並作為該成員的一個默認實現,在以后,在實現該接口的時候,如果實現了該接口的成員,則會被覆蓋默認實現,否則、它的實現依然會使用接口中定義的默認實現。
二、主要應用場景:
在不破壞影響已有實現的情況下,可以添加新成員。這解決了在第三方已經大量使用了的接口上進行擴展帶來問題的痛點。
三、規則與限制:
1. 支持的成員:方法、屬性、索引器、 及各種靜態成員。不支持實例字段、實例事件、自動屬性、實例構造和析構函數
2. 支持修飾符:private, protected, internal, public, virtual, abstract, sealed, static, extern, and partial.
3. 默認訪問級別為public,可以顯式指定,也可以不指定。
4. 除過sealed和private修飾的方法體之外,其他帶有方法體的成員默認都是virtural成員
5. 接口中的默認實現只屬於該接口和繼承它的子接口,但不能被它的實現繼承,所以只能通過接口變量調用。除非接口的實現中進行了再次實現。
6. 多層次繼承的接口,調用最接近實現的接口的默認實現。也就是“層次最接近、最新實現最近、同級的new比overrided更接近”。
7. 在類中實現並覆蓋接口中的成員,無需用new和override關鍵字,與接口的實現機制是保持一致,無需任何修改或操作。
四、實現舉例:
1. 先定義一個接口IFlyable,代碼如下:
public interface IFlyable { //支持const常量 public const int MAX_SPEED = 200; const int MIN_SPEED = 0; //默認public,可以省略 public const string SPEED_UOM = "m/s"; private static readonly Dictionary<string, string> nameDic; //支持靜態構造函數,不支持實例構造函數 static IFlyable() { nameDic = new Dictionary<string, string>() { {nameof(MAX_SPEED),MAX_SPEED.ToString()}, {nameof(MIN_SPEED),MIN_SPEED.ToString()} }; } //支持索引器,但是其中的變量也只能是靜態變量。 string this[string key] { get { string tmp; if (nameDic.ContainsKey(key)) { tmp = nameDic[key]; } else { tmp = string.Empty; } return tmp; } } int Speed { get;} //默認為public和virtual,所以此處的virtual和public可有可無 public void Initialize() { var defaultSpeed = AverageSpeed(); Initialize(defaultSpeed); WriteLine($"{nameof(IFlyable) + "." + nameof(Initialize)} at default {defaultSpeed} {SPEED_UOM}"); } // 私有帶有方法體的成員是允許存在的 private int AverageSpeed() { return (MAX_SPEED + MIN_SPEED) / 2; } void Initialize(int speed); //默認為public和virtual,所以此處的virtual和public可有可無 void Fly() { WriteLine($"{nameof(IFlyable) + "." + nameof(Fly)}"); } }
2. 再定義一個IAnimal接口:
public interface IAnimal { //默認為public和virtual,可以顯式指出該成員時virtual void SayHello() { WriteLine($"{nameof(IAnimal) + "." + nameof(SayHello)}"); } void Walk() { WriteLine($"{nameof(IAnimal) + "." + nameof(Walk)}"); } }
3. 定義一個IFlyableAnimal接口,繼承自前兩個接口
public interface IFlyableAnimal:IAnimal,IFlyable { public new const int MAX_SPEED = 300; //重寫IAnimal的SayHello, void IAnimal.SayHello() { WriteLine($"override {nameof(IFlyableAnimal) + "." + nameof(SayHello)} "); } //因為IFlyableAnimal接口繼承了IAnimal的接口,加new關鍵字來隱藏父類的繼承的SayHello,可以不加,但會有警告。 public new void SayHello() { WriteLine($"new {nameof(IFlyableAnimal) + "." + nameof(SayHello)}"); } //因為IFlyableAnimal接口繼承了IFlyable的接口,接口繼承的默認實現是無法用override關鍵字的 public void Walk() { WriteLine($"new {nameof(IFlyableAnimal) + "." + nameof(Walk)}"); } }
4. 定義一個類Sparrow,來實現接口IFlyableAnimal
public class Sparrow : IFlyableAnimal { //實現IFlyable中的Speed接口 public int Speed { get; private set; } //實現IFlyable中的Initialize(int speed)接口 public void Initialize(int speed) { this.Speed = speed; WriteLine($"{nameof(Sparrow) + "." + nameof(Initialize)} at {Speed} {IFlyable.SPEED_UOM}"); } //實現並覆蓋接口中的SayHello,無需用new和override關鍵字,與接口的實現機制是保持一致,無需任何修改或操作。 public virtual void SayHello() { // 注意的使用IFlyableAnimal.SPEED_UOM,類只能實現接口,不能繼承接口的默認實現,但是接口可以繼承父接口的默認實現 WriteLine($"{nameof(Sparrow) + "." + nameof(SayHello)} at {Speed} {IFlyableAnimal.SPEED_UOM}"); } }
5. 對前面的定義進行調用
static void Main(string[] args) { Sparrow bird = new Sparrow(); bird.Initialize(98); //Sparrow中實現並覆蓋了Initialize,所以可以直接用類調用 bird.SayHello();//Sparrow中實現並覆蓋了,所以可以直接用類調用 //bird.Fly(); Fly不可訪問,因為Bird沒有實現也不會繼承接口中的實現,所以不擁有Fly方法。 //IFlyableAnimal 繼承了IAnimal和IFlyable的默認實現,通過該變量調用SayHello和Fly方法 IFlyableAnimal flyableAnimal = bird; flyableAnimal.SayHello(); flyableAnimal.Fly(); //IFlyableAnimal繼承自IAnimal和IFlyable,而Sparrow類又繼承自了IFlyableAnimal,所以可以用IAnimal和IFlyable變量調用 IAnimal animal = bird; animal.SayHello(); IFlyable flyable = bird; flyable.Initialize(); flyable.Fly(); Monster monster = new Monster(); IAlien alien = monster; //alien.Fly();//編譯器無法分清是'IBird.Fly()' 還是 'IInsect.Fly()' } //輸出: //Sparrow.Initialize at 98 m/s //Sparrow.SayHello at 98 m/s //Sparrow.SayHello at 98 m/s //IFlyable.Fly //Sparrow.SayHello at 98 m/s //Sparrow.Initialize at 100 m/s //IFlyable.Initialize at default 100 m/s //IFlyable.Fly
五、多層次繼承產生的問題
因為接口時可以多重繼承的,這樣就會出現類似C++里產生菱形繼承問題。如下圖所示,IBird和IInsect都繼承了IFlyable,而IAlien又同時繼承IBird和IInsert兩個接口,並做了新的實現,這時,他們就構成了一個菱形或者鑽石形狀。這時候,實現了IAlien的Monster類,就會無法分清改用哪個Fly
代碼如下:
public interface IBird : IFlyable { void Fly() { WriteLine($"{nameof(IBird) + "." + nameof(Fly)}"); } } public interface IInsect : IFlyable { void Fly() { WriteLine($"{nameof(IInsect) + "." + nameof(Fly)}"); } } public interface IAlien : IBird, IInsect { } public class Monster : IAlien { public int Speed { get; private set; } = 1000; public void Initialize(int speed) { this.Speed = speed; } }
下面調用語句alien.Fly就會導致混淆,編譯器無法分清此Fly到底是IBird.Fly() 還是IInsect.Fly():
static void Main(string[] args) { Monster monster = new Monster(); IAlien alien = monster; //alien.Fly();//編譯器無法分清是'IBird.Fly()' 還是 'IInsect.Fly()' }
對於這種問題的解決方案,是要么通過更為具體的接口(IBird或IInsect)來調用相應成員,要么在Monster類中實現Fly,再通過類來調用。
六、總結
C#8.0的接口默認實現對於軟件的功能的擴展提供了比較大的靈活性,同時,也引入了一些規則,使得掌握其的成本增加。這里,我盡力求對其一些規則等做出了總結,並展現了示例予以說明,肯定又不足指出,希望大家指正。