窺探未來:C# 8 的預設介面方法

溫馨提醒:撰寫此文時,我手邊沒有任何編譯器能夠編譯這個新語法。所以,文中的範例程式碼不保證可以運行,而且保證不能運行。此外,本文提及的 C# 8 新功能雖已通過提案,但不代表將來 C# 8 正式發布時一定會納入。

什麼是預設介面方法?

預設介面方法(default interface methods) 或者說「預設介面實作」,是 C# 8 的新語法,它能夠讓設計 API 介面的人在將來需要的時候對介面中原本已存在的方法添加預設實作,而且不至於影響既有的用戶端程式碼(即原本實作了該介面的類別都還能夠繼續正常運作)。簡單來說,就是「讓 API 能持續進化,同時維持相容性。」

這種功能也有人叫它 trait。這個術語在程式語言的領域裡常用來指稱一種能夠將某些方法的實作程式碼注入至其他類別的技術。有一點擴充方法(extension methods)的味道,對嗎?其實,不少程式語言都已經有這項功能,例如 Java、PHP、Rust 等等,其中 Rust 就直接以 trait 作為此語法的關鍵字。現在,C# 語言終於也(將要)有對應的語法了。

「預設介面方法」不是單純的語法糖。就我目前看到的資料,欲實現此功能,CLR 很可能需要配合做一些修改。此外,根據官方文件,這項功能還能夠讓 C# 更容易跟 Android (Java) 與 iOS (Swift) 應用程式互動。

範例程式

來看一個簡單的例子:

public interface ILogger
{
    // 這是原本我們熟悉的、沒有實作的介面方法。
    void Error(Exception ex); 
    
    // 此方法提供了預設實作。C# 8 以後才能這樣寫。
    void Log(string msg) 
    { 
        Console.WriteLine(msg); 
    }
}

public class Foo : ILogger
{
    // 沒有實作 ILogger.Log 方法! C# 8 沒問題,C# 7 編譯失敗。

    // 由於 Error 方法沒有預設實作,所以還是要寫。
    public Error(Exception ex) { ... }
}

現在,如果類別 Foo 實作了多個介面,而那些介面也都帶有預設實作,是否又帶了點多重繼承的味道?事實上,在一篇論文裡面就有提到,traits 的一個用意就是為了補強單一繼承的程式語言(如 C# 和 Java),提供開發人員多一個選擇,以便透過「組合」(而非多重繼承)的方式來為類別擴充能力,並減少重複的程式碼。

想像一下,你是設計 API 的人,有權決定如何改良 ILogger 介面。當你發布此 API 的第一個版本之後,有些開發人員已經使用你的介面來實作一些類別,當然,在他們的類別裡面,必定有實作 ILogger 的 Log 方法。在後續的 ILogger 版本,基於某些原因,你決定為 Log 加上預設實作,讓別人在實作此介面時省點力氣,同時又不會衝擊既有的類別。這是預設介面方法的一種應用場景。

剛才的範例已經展示了預設介面方法的語法,底下是用戶端的部分,跟以前的寫法完全一樣:

ILogger logger = new Foo();
logger.Log("....");

值得一提的是,不能這樣寫:

Foo foo = new Foo();
foo.Log("....");  // 編譯錯誤!

編譯錯誤的原因是,預設方法 Log 只存在介面裡,而不會被類別 Foo 繼承下來。

小結

預設介面方法並沒有引進什麼複雜的語法,它寫起來相當簡單(至少就我目前所知而言)。然而,就跟多數新增的語法一樣,實際上都還是有不少細節是將來寫程式時需要留意的,例如:介面中的屬性、索引子、事件也都可以有預設實作嗎?搭配其他修飾詞使用時有什麼要注意的?可以在介面中提供靜態方法的預設實作嗎?這些都是我的疑問,等到手邊有能夠編譯 C# 8 語法的編譯器時,再來實驗看看。

最後,「介面就如同一紙合約,沒有任何實作」這句話得要改了。可以想像,好多文件都要跟著改。

有得忙了 ^_^

參考資料與延伸閱讀
窺探未來:C# 8 的預設介面方法 窺探未來:C# 8 的預設介面方法 Reviewed by Michael Tsai on 7/11/2018 Rating: 5
技術提供:Blogger.