C# 8: 默認接口方法


翻譯自 John Demetriou 2018年8月4日 的文章 《C# 8: Default Interface Methods》[1],補充了一些內容

C# 8 之前

今天我們來聊一聊默認接口方法。聽起來真的很奇怪,不是嗎?接口僅用於定義契約。接口的實現類會擁有一組公共方法,不過實現類被賦予了以其自己的方式實現每個方法的自由。目前為止,如果我們還需要為這些方法中的一個或多個方法提供實現,我們將使用繼承。
如果我們希望這個類不是實現所有方法,而只是實現其中的一個子集,我們可以將這些方法和類本身抽象(abstract)。

例如,我們不能這么寫:

interface IExample
{
    void Ex1();                                      // 允許
    void Ex2() => Console.WriteLine("IExample.Ex2"); // 不允許(C# 8 以前)
}

我們不得不用下面的抽象類來替代:

abstract class ExampleBase
{
    public abstract void Ex1();
    public void Ex2() => Console.WriteLine("ExampleBase.Ex2");
}

不過還好,這已經足夠滿足我們的大部分需求了。

C# 8 之后

那么,有什么改變嗎?為什么我們需要引入這個新特性?我們錯過了什么並且從未注意到我們錯過了什么?

菱形問題

由於菱形問題[2],C#(以及許多其他語言)不支持多重繼承。為了允許多重繼承,同時避免菱形問題,C# 8 引入了默認接口方法。

從 C# 8 開始,使用默認接口方法,您可以擁有一個接口定義,以及該定義中某些或所有方法的默認實現。

interface IExample
{
    void Ex1();                                      // 允許
    void Ex2() => Console.WriteLine("IExample.Ex2"); // 允許
}

因此,現在您可以實現一個含有已實現方法的接口,並且可以避免希望從特定類(也包含通用方法)繼承的類中的代碼重復。

使用默認接口方法,菱形問題並沒得到百分之百解決。當一個類繼承自從第三個接口繼承而來的兩個接口,並且所有接口都實現了相同方法時,仍然可能發生這種情況。
在這種情況下,C# 編譯器將根據當前上下文選擇調用適當的方法。如果無法推斷出特定的哪一個,則會顯示編譯錯誤。

例如,假設我們有以下接口:

interface IA
{
    void DoSomething();
}

interface IB : IA
{
    void DoSomething() => Console.WriteLine("I am Interface B");
}

interface IC : IA
{
    void DoSomething() => Console.WriteLine("I am Interface C");
}

然后,我創建一個實現上述兩個接口的類 D,會引發一個編譯錯誤:

//編譯器提示:“D”未實現接口成員“IA.DoSomething()”
public class D : IB, IC
{ }

但是,如果類 D 實現它自己版本的 DoSomething 方法,那么編譯器將知道調用哪個方法:

public class D : IB, IC
{
    public void DoSomething() => Console.WriteLine("I am Class D");
}

若 Main 方法代碼如下:

static void Main()
{
    var x = new D();
    x.DoSomething();
    Console.ReadKey();
}

運行程序,控制台窗口輸出:I am Class D

其他益處

使用方法的默認接口實現,API 提供者可以擴展現有接口而不破壞遺留代碼的任何部分。

Trait 模式

譯者注:
在計算機編程中,特征(Trait)是面向對象編程中使用的一個概念,它表示可用於擴展類的功能的一組方法。[3]

Trait 模式大體上就是多個類需要的一組方法。
在此之前,C# 中的 Trait 模式是使用抽象類實現的。但是由於多重繼承不可用,實現 Trait 模式變得非常棘手,所以大多數人要么避開它,要么迷失在一個巨大的繼承鏈中。

不過,在接口中使用默認方法實現,這將發生改變。我們可以通過在接口中使用默認接口方法實現,提供一組需要類擁有的方法,然后讓這些類繼承此接口。
當然,任何一個類都可以用它們自己的實現覆蓋這些方法,但是以防它們不希望這么做,我們為它們提供了一組默認的實現。

以下為譯者補充

接口中的具體方法

默認接口方法的最簡單形式是在接口中聲明具體方法,該方法是具有主體部分的方法。

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

實現此接口的類不必實現其具體方法。

class C : IA { } // OK

static void Main()
{
    IA i = new C();
    i.M(); // 輸出 "IA.M"
}

CIA.M 的最終替代是在 IA 中聲明的具體方法 M
請注意,類只能實現接口,而不會從接口繼承成員

C c = new C(); // 或者 var c = new C();
c.M();         // 錯誤: 類 'C' 不包含 'M' 的定義

但如果實現此接口的類也實現了具體方法,則同一般的接口含義是一樣的:

class C : IA
{
    public void M() { Console.WriteLine("C.M"); }
}

static void Main()
{
    IA i = new C();
    i.M(); // 輸出 "C.M"
}

子接口如何調用父接口的方法?

這是 willamyao 提的一個挺有意思的問題。乍一看,現實中還會遇到這樣的需求?細想一下,還真的可能會用到。下面就來演示一個簡單的示例,在接口 IB 中調用父接口 IA 中的成員方法 M,代碼如下:

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

interface IB : IA
{
    //void IA.M() { Console.WriteLine("IB.M"); }

    void IB_M() { M(); }
}

class C : IB { }

static void Main(string[] args)
{
    IB i = new C();
    i.IB_M();  // 輸出 "IA.M";如果把 IB 中的注釋行打開,這里會輸出 "IB.M"
}

作者 : John Demetriou
譯者 : 技術譯民
出品 : 技術譯站
鏈接 : 英文原文


  1. https://www.devsanon.com/c/c-8-default-interface-methods/ C# 8: Default Interface Methods ↩︎

  2. https://www.cnblogs.com/ittranslator/p/13838080.html 菱形問題 ↩︎

  3. https://en.wikipedia.org/wiki/Trait_(computer_programming) Trait ↩︎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM