介面 - 定義多個類型的行為

小提示

剛開始開發軟體嗎? 先從 入門 教學開始。 當你需要定義跨無關類型共享行為時,就會遇到介面。

有其他語言的經驗嗎? C# 介面類似於 Java 中的介面或 Swift 中的協定。 瀏覽 明確實作 部分以尋找 C# 特定模式。

介面定義了一個契約:一組相關的方法、屬性、事件和索引器,這些必須由classstruct實作。 介面允許單一型態實作多個合約,這很重要,因為 C# 不支援多個類別繼承。 結構體不能繼承其他結構或類別,因此介面是唯一能在結構型別間加入共享行為的方式。

以下範例宣告了一個介面以及實作該介面的類別:

interface IEquatable<T>
{
    bool Equals(T obj);
}
public class Car : IEquatable<Car>
{
    public string? Make { get; set; }
    public string? Model { get; set; }
    public string? Year { get; set; }

    public bool Equals(Car? car) =>
        car is not null &&
        (Make, Model, Year) == (car.Make, car.Model, car.Year);
}

任何實作 IEquatable<T> 的類別或結構體都必須提供 Equals 與介面簽章相符的方法。 你可以信賴任何 IEquatable<T> 實作都會支援等式比較,無論具體類型為何。 這種可預測性正是介面的核心價值。

宣告一個介面

用關鍵字定義一個介面 interface 。 依慣例,介面名稱以大寫 I字母開頭:

interface ILogger
{
    void Log(string message);
    string Name { get; }
}

介面可以包含方法、屬性、事件和索引器。 介面不得包含執行個體欄位、執行個體建構函式或終結器。 預設成員是 public 。 你需要時也可以指定其他無障礙修飾符。 例如,對不應在程式集外可見的成員使用 internal

實作介面

類別或結構體在其宣告冒號後列出它實作的介面。 該類別必須為介面中宣告的每個成員提供實作:

public class ConsoleLogger : ILogger
{
    public string Name => "Console";

    public void Log(string message) =>
        Console.WriteLine($"[{Name}] {message}");
}

public class FileLogger : ILogger
{
    public string Name => "File";

    public void Log(string message)
    {
        // In a real app, write to a file
        Console.WriteLine($"[{Name}] Writing to file: {message}");
    }
}

一個類別可以實作多個介面,並以逗號分隔。 它必須為其列出的每個介面中的所有成員提供實作。

明確實現

有時你需要實作介面成員,但不讓它成為類別的公開 API。 明確實作 則以介面名稱限定成員。 該成員僅能透過介面類型的變數存取:

interface IMetric
{
    double GetDistance(); // Returns meters
}

interface IImperial
{
    double GetDistance(); // Returns feet
}

public class Runway(double meters) : IMetric, IImperial
{
    // Explicit implementation for IMetric
    double IMetric.GetDistance() => meters;

    // Explicit implementation for IImperial
    double IImperial.GetDistance() => meters * 3.28084;
}

當兩個介面宣告同名成員,或想保持類別的公共介面乾淨時,明確實作非常有用。 欲了解更多細節,請參閱 明確介面實作

介面繼承

介面可以繼承一或多個其他介面。 實作衍生介面的類別必須實作該衍生介面及其所有基礎介面的所有成員:

interface IDrawable
{
    void Draw();
}

interface IShape : IDrawable
{
    double Area { get; }
}

public class Circle(double radius) : IShape
{
    public double Area => Math.PI * radius * radius;

    public void Draw() =>
        Console.WriteLine($"Drawing circle with area {Area:F2}");
}

實作 的 IShape 類別可以隱式轉換為 IDrawable,因為 IShape 繼承自 。

介面與抽象類別的比較

介面與抽象類別都定義了衍生型別必須滿足的契約。

  • 當相關類型共享狀態(欄位)、建構子或非公開成員時,請使用抽象類別。 抽象類別讓你透過新增預設行為的新成員來演化階層結構,而不破壞現有的導出型別。
  • 使用介面,當型別需要完成跨越無關階層的合約,或需要實作多個合約時。 介面無法宣告實例欄位或建構子,因此最適合為已有基底類別的型別新增能力。 對於進階情境,介面也支援 預設成員實作

一個類別只能繼承一個基底類別,但可以實作多個介面。 這種區分常使介面成為定義跨類型階層能力的更好選擇。

使用內部介面

通常,只要介面簽章中的所有類型都能公開存取,就可以實作一個內部介面,並包含公開成員。 當介面在其成員簽章中使用內部型別時,必須使用明確實作,因為實作的成員不能是公開的,同時又暴露內部型別。

internal class InternalConfiguration
{
    public string Setting { get; set; } = "";
}

internal interface ILoggable
{
    void Log(string message);
}

internal interface IConfigurable
{
    void Configure(InternalConfiguration config);
}

public class ServiceImplementation : ILoggable, IConfigurable
{
    // Implicit implementation: ILoggable uses only public types in its signature
    public void Log(string message) =>
        Console.WriteLine($"Log: {message}");

    // Explicit implementation: IConfigurable uses internal types
    void IConfigurable.Configure(InternalConfiguration config) =>
        Console.WriteLine($"Configured with: {config.Setting}");
}

在前述範例中,IConfigurable 的方法簽章使用了內部型別 InternalConfigurationServiceImplementation 使用明確的實作來對該成員進行操作。 相比之下,ILoggable 僅在其簽章中使用公開型別(string),且可以被隱式實作。

預設介面成員與靜態抽象成員

介面支援兩項超越基本合約的進階功能:

  • 預設介面成員 讓介面提供方法主體。 實作型別會繼承預設實作,並可選擇性地覆寫它。 如需詳細資訊,請參閱預設介面方法
  • 靜態抽象成員 需要實作型別來提供靜態成員,這對於定義運算子合約或工廠模式非常有用。 欲了解更多資訊,請參閱 介面中的靜態抽象成員

這兩個功能都在語言參考中關於 介面 的文章中有介紹。 大多數日常介面使用都涉及宣告與實作本文前述的模式。

介面摘要

  • 介面定義了方法、屬性、事件與索引器的契約。
  • 實作介面的類別或結構體必須為所有宣告成員提供實作(除非介面提供預設實作)。
  • 你無法直接實例化介面。
  • 類別或結構可以實作多個介面。 類別可以繼承基底類別,也會實作一或多個介面。
  • 介面名稱通常以 開頭。I