【5min+】 巨大的爭議?C# 8 中的接口

系列介紹

【五分鐘的dotnet】是一個利用您的碎片化時間來學習和豐富.net知識的博文系列。它所包含了.net體系中可能會涉及到的方方面面,好比C#的小細節,AspnetCore,微服務中的.net知識等等。
5min+不是超過5分鐘的意思,"+"是知識的增長。so,它是讓您花費5分鐘如下的時間來提高您的知識儲備量。git

正文

伴隨着 .NET Core 3.0 一塊兒發佈的 C# 8 ,從發佈至今已通過了快大半年了。若是您細心的話,就能發如今C# 8新增的功能中有一條:「默認接口方法」 。 半年前當我看到這一新特性的時候,我驚呆了,可是驚訝之餘是更多的疑惑。由於對於接口這個東西來講,從C#發佈至今的十多年裏幾乎一直保持它的樣子,然而在C# 8以後,它有了巨大的變化。隨着而來,也是各類爭論的聲音。github

很早以前我就想寫這篇文章了,可是因爲各類緣由一直拖延到了如今。面試

先讓咱們來回顧一下 C# 中原有的接口有什麼特色:c#

  • 接口相似於只有抽象成員的抽象基類。 實現接口的任何類或結構都必須實現其全部成員。
  • 接口沒法直接進行實例化。 其成員由實現接口的任何類或結構來實現。
  • 接口能夠包含事件、索引器、方法和屬性。
  • 接口不含方法的實現。
  • 一個類或結構能夠實現多個接口。 一個類能夠繼承一個基類,還可實現一個或多個接口。

也正是基於這些特色,當咱們在接口中爲一個方法加上"pulic"等關鍵字的時候,編譯器會提示咱們這是一個錯誤的寫法:async

interface IRepository
{
    //Compile-time error CS0106 The modifier 'public' is not valid for this item.
    public void Add();
}

因此更不用談給方法寫一個實現了。這就讓它和 C# 中的另一種事物行成了鮮明的對比,是的,抽象類。不知道你們有沒有在各類面試中遇到過這樣的提問:「接口能有任何的訪問修飾符嗎?」,「接口和抽象類的區別是什麼?」微服務

曾經您能夠和天然的脫口而出答案:「沒有修飾符。一個能夠有默認方法,一個只能申明方法…………」。 可是從如今開始:這些答案是錯的了。😂學習

這是微軟MSDN中的設計規範截圖:this

x

上面的圖是我半年前截的圖,今天原本想去找對應的連接分享出來,可是發現找不到了。可能…………編碼

x

新的接口

好了,說了那麼多,咱們來看看C# 8 爲咱們改變後的接口是什麼樣子:.net

enum LogLevel
{
    Information,
    Warning,
    Error
}

interface ILogger
{
    void WriteCore(LogLevel level, string message);

    void WriteInformation(string message)
    {
        WriteCore(LogLevel.Information, message);
    }

    void WriteWarning(string message)
    {
        WriteCore(LogLevel.Warning, message);
    }

    void WriteError(string message)
    {
        WriteCore(LogLevel.Error, message);
    }
}

class ConsoleLogger : ILogger
{
    public void WriteCore(LogLevel level, string message)
    {
        Console.WriteLine($"{level}: {message}");
    }
}

class TraceLogger : ILogger
{
    public void WriteCore(LogLevel level, string message)
    {
        switch (level)
        {
        case LogLevel.Information:
            Trace.TraceInformation(message);
            break;

        case LogLevel.Warning:
            Trace.TraceWarning(message);
            break;

        case LogLevel.Error:
            Trace.TraceError(message);
            break;
        }
    }
}

ILogger consoleLogger = new ConsoleLogger();
consoleLogger.WriteWarning("Cool no code duplication!");  // Output: Warning: Cool no Code duplication!

ILogger traceLogger = new TraceLogger();
consoleLogger.WriteInformation("Cool no code duplication!");  // Cool no Code duplication!

這是我在網上摘取的一部分代碼。是的,您沒有看錯,接口能夠實現方法了。而且還能夠給它添加上訪問修飾符:

interface IDemoInterface
{
    public static int staticIntValue = 123;   //Right

    public void PulicMethod(){ }  //Right
}

就像您所見的同樣,它還能夠在內部聲明靜態的數據。

可是下面的寫法依舊會提示錯誤哦:

interface IDemoInterface
{
    abstract void M1() { } //Error. 由於有abstract
    abstract private void M2() { } //Error
    abstract static void M3() { } //Error
    static extern void M4() { } //Error.由於有extern
}

爭議點

走到這裏,也許您會說:「這不挺好的嗎?好像對我也沒有啥影響。」 確實,假如您不更改接口的簽名,不管您是否在接口中增長默認實現仍是某些靜態數據都不會對已有的應用程序形成任何錯誤。

可是若是您常用抽象類的話,您就會發現,這樣的接口是否是和抽象類太像了?甚至有點徹底掩蓋了抽象類的優點。

當我半年前看到這一新特性時,我就產生了這樣的疑惑。 這個 「默認方法實現」 的新特性,真的須要嗎?若是須要,那我如何選擇它和抽象類?

結果我發現,你們都對這一特性產生了困惑:

x

x

於時,我抱着懷疑的態度在網上處處搜索答案。最後在C# 官方團隊的筆記中我看到了這樣一句話:

x

這句話的意思大體是:咱們應該更深刻地研究Java在這裏所作的事情,Java對接口的實現很好,咱們應該…………(有關該說明的github連接能夠點擊這裏)。

我當時心就涼了半截。不過緩了緩,我鎮定的思考了一下:好的語言設計被借鑑和參考也是頗有必要的。 好比如今其它語言都在借鑑C#的await和async。(PS:C#和Typescript怎麼愈來愈像😝)。

那麼咱們真的須要在接口中提供默認實現嗎?那什麼狀況下我須要這樣作? 畢竟我們使用了 C# 這麼多年,就算接口沒有提供默認實現也能設計出很好的系統來。因此爲了解決上面的疑問,仍是得回到接口和抽象類的本質。

按照我們以往使用接口和抽象類的狀況來看:接口表示的是一種行爲,"who can"(好比鳥會飛),而基類表示的是一種類別,"is a"(好比麻雀是鳥)。 所以在OOP的世界中,若是我們細心的來建模的話,咱們會把表示行爲的共性抽象爲一個接口:好比鳥會飛,我們能夠抽象一個IFly的接口。對老版本的 C# 來講,不能提供方法的實現,因此只會有一個Fly() 的方法簽名。而如今咱們經過新的特性,咱們能夠給「飛」這個動做提供一個默認的實現,好比 90%的鳥都是「煽動翅膀起飛」,則咱們能夠將這個大部分 的操做做爲默認實現,而對那些10%的 「小衆」 進行重寫。 也正是因爲接口更關注的是「行爲」,因此接口中不能存在「狀態」,所以您會發現雖然能夠聲明字段了,可是隻能聲明靜態字段。而實例化的狀態信息依舊只能經過抽象類來實現。

固然,在如今接口和抽象類建模比較模糊的今天,從技術的實現上來講,其實接口的默認實現並無帶來不少技術編碼上的好處。可是若是您堅持好的規範抽象,好比接口開頭就是用「I」,將對象的行爲進行抽象提高爲接口,也許某一刻您會感覺到該特性帶來的改變。

最後,小聲說一句:一鍵三連……。 哦,不對,點個推薦吧.....

x

相關文章
相關標籤/搜索