在C#8.0中,針對介面引入了一項新特性,就是可以指定預設實現,方便對已有實現進行擴展,也對面向Android和Swift的Api進行互操作提供了可能性。下麵我們來看看該特性的具體規則與實現。 一、主要應用場景: 在不破壞影響已有實現的情況下,可以添加新成員。這解決了在第三方已經大量使用了的介面 ...
在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的介面預設實現對於軟體的功能的擴展提供了比較大的靈活性,同時,也引入了一些規則,使得掌握其的成本增加。這裡,我儘力求對其一些規則等做出了總結,並展現了示例予以說明,肯定又不足指出,希望大家指正。