共用方式為


19 個介面

19.1 一般規定

介面會定義合約。 實作介面的類別或結構應遵守其合約。 介面可能繼承自多個基底介面,而類別或結構可能會實作多個介面。

介面可能包含各種類型的成員,如 §19.4 中所述。 介面本身可能會為其宣告的部分或所有函式成員提供實作。 介面未提供實作的成員是抽象的。 其實作必須由實作介面的類別或結構提供,或提供覆寫定義的衍生介面。

附註: 從歷史上看,將新的函式成員新增至介面會影響該介面類型的所有現有取用者;這是一個重大的改變。 新增介面函式成員實作可讓開發人員升級介面,同時仍可讓任何實作者覆寫該實作。 介面的使用者可以接受實作作為非重大變更;不過,如果其需求不同,則可以置換提供的實作。 尾註

19.2 介面宣告

19.2.1 一般規定

interface_declaration 是一種宣告新介面類型的 type_declaration§14.7)。

interface_declaration
    : attributes? interface_modifier* 'partial'? 'interface'
      identifier variant_type_parameter_list? interface_base?
      type_parameter_constraints_clause* interface_body ';'?
    ;

interface_declaration由一組可選的屬性§23) 組成,後面接著一組可選的 interface_modifiers (§19.2.2) ,後面接著可選的部分修飾符 (§15.2.7) ,後面接著命名介面的關鍵字interface識別碼,後面接著選擇性 variant_type_parameter_list 規格 (§19.2.3) ,後面接著選擇性interface_base規格 (§19.2.4),後面接著選擇性type_parameter_constraints_clause規格 (§15.2.5) ,後面接著interface_body§19.3) ,後面接著選擇性分號。

除非介面宣告也提供 variant_type_parameter_list,否則介面宣告不得提供 type_parameter_constraints_clause

提供 variant_type_parameter_list 的介面宣告是泛型介面宣告。 此外,泛型類別宣告或泛型結構宣告內巢狀的任何介面本身都是泛型介面宣告,因為應該提供包含型別的型別自變數來建立建構型別 ({8.4)。

19.2.2 介面修飾符

interface_declaration可以選擇性地包含一連串的介面修飾詞:

interface_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier§24.2) 僅適用於不安全代碼 (§24)。

同一個修飾詞在介面宣告中出現多次是編譯時期錯誤。

new只有在類別內定義的介面上才允許修飾詞。 它會指定介面會以相同名稱隱藏繼承的成員,如 \15.3.5 中所述

publicprotectedinternalprivate 修飾詞可控制 介面的存取範圍。 視介面宣告發生的內容而定,可能只允許其中一些修飾詞 ({7.5.2)。 當部分類型宣告(§15.2.7)包含存取權限規範時(透過 publicprotectedinternalprivate 修飾詞),就會套用 §15.2.2 中的規則。

19.2.3 變體類型參數清單

19.2.3.1 一般規定

變體類型參數清單只能在介面和委託類型中使用。 與一般的type_parameter_list差異在於每個類型參數上可選的variance_annotation

variant_type_parameter_list
    : '<' variant_type_parameter (',' variant_type_parameter)* '>'
    ;

variant_type_parameter
    : attributes? variance_annotation? type_parameter
    ;

variance_annotation
    : 'in'
    | 'out'
    ;

如果變異數註釋為 out,則類型參數會稱為 covariant。 如果變異數註釋為 in,則類型參數會稱為 反變數。 如果沒有變異數註釋,則類型參數會說為不變異

範例:在下列內容中:

interface C<out X, in Y, Z>
{
    X M(Y y);
    Z P { get; set; }
}

X 為共變數, Y 為反變數,且 Z 為不變異。

結束範例

如果泛型介面在多個元件中宣告 ({15.2.3),則每個部分宣告都應該為每個類型參數指定相同的變異數。

19.2.3.2 變異安全

類型參數清單中的變異數批註會限制類型宣告中類型可以出現的位置。

如果下列其中一項保留,類型 T 為輸出不安全

  • T 是反變數類型參數
  • T 是具有輸出不安全元素類型的陣列類型
  • T是從泛型型別建構的介面或委派類型Sᵢ,... AₑS<Xᵢ, ... Xₑ>,其中至少有一個Aᵢ符合下列條件之一:
    • Xᵢ 為共變數或非變異,且 Aᵢ 為 output-unsafe。
    • Xᵢ 是反變數或非變異,且 Aᵢ 為 input-unsafe。

如果下列其中一項保留,類型 T 為輸入不安全

  • T 是 covariant 類型參數
  • T 是一種具有輸入不安全元素型別的陣列型別
  • T是從泛型型別建構的介面或委派類型S<Aᵢ,... Aₑ>S<Xᵢ, ... Xₑ>,其中至少有一個Aᵢ符合下列條件之一:
    • Xᵢ 為共變數或非變異,且 Aᵢ 為 input-unsafe。
    • Xᵢ 是反變數或非變異,且 Aᵢ 為 output-unsafe。

在直覺上,輸出不安全的類型在輸出位置中被禁止,而輸入不安全的類型則禁止在輸入位置中。

如果類型不是 output-unsafe,則為輸出安全,如果不是 input-unsafe,則為輸入安全。

19.2.3.3 變異數轉換

變異數標註的目的是提供更寬鬆但仍然類型安全的轉換到介面和委派類型。 為此,隱含轉換(~10.2)和明確轉換的定義(•10.3)會利用變異數轉換的概念,其定義如下:

如果 T<Aᵢ, ..., Aᵥ> 類型是使用 variant 類型參數T<Bᵢ, ..., Bᵥ>宣告的介面或委派類型,且每個 Variant 類型參數T都保留下列其中一項,則類型T<Xᵢ, ..., Xᵥ>可轉換成類型Xᵢ

  • Xᵢ 是協變的,並且存在從 AᵢBᵢ 的隱含參考或識別轉換。
  • Xᵢ 是逆變,而且有從 BᵢAᵢ 的隱含參考或身份識別轉換
  • Xᵢ 是不變的,且身分識別轉換存在從 AᵢBᵢ

19.2.4 基本介面

介面可以繼承自零個或多個介面類型,這些類型稱為介面的 明確基底介面。 當介面有一或多個明確的基底介面時,在該介面的宣告中,介面標識符後面接著冒號和基底介面類型的逗號分隔清單。

衍生介面可以宣告隱藏基底介面中宣告的繼承成員 (§7.7.2.3) 的新成員,或明確實作基底介面中宣告的繼承成員 (§19.6.2) 。

interface_base
    : ':' interface_type_list
    ;

明確基底介面可以建構介面類型 (§8.4§19.2) 。 基底介面本身不能是型別參數,不過它可以牽涉到範圍中的型別參數。

針對已構建的介面類型,明確基底介面的形成是通過在泛型類型宣告中採用明確的基底介面宣告,然後將基底介面宣告中的每個 type_parameter 替換成構建類型中對應的 type_argument

介面的明確基底介面應至少可以和介面本身一樣可存取(~7.5.5)。

注意:例如,在 private 介面的 internal 中指定 public 介面是編譯時期錯誤。 尾註

這是介面直接或間接繼承自本身的編譯時間錯誤。

介面的 基底介面是明確的基底介面及其基底介面。 換句話說,基底介面的集合是對明確基底介面、其明確基底介面等等的完整可傳遞閉包。 介面會繼承其基底介面的所有成員。

範例:在下列程式代碼中

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

基底介面 IComboBoxIControlITextBoxIListBox。 換句話說,IComboBox上述介面會繼承成員SetTextSetItems以及Paint

結束範例

繼承自建構泛型型別的成員會在型別替代之後繼承。 也就是說,成員中的任何組成型別都有基類宣告的類型參數,以class_base規格中使用的對應型別自變數取代。

範例:在下列程式代碼中

interface IBase<T>
{
    T[] Combine(T a, T b);
}

interface IDerived : IBase<string[,]>
{
    // Inherited: string[][,] Combine(string[,] a, string[,] b);
}

將型別參數IDerived替換成Combine後,介面T會繼承string[,]方法。

結束範例

實作介面的類別或結構也會隱含實作介面的所有基底介面。

在部分介面宣告(§15.2.7)的多個部分上處理介面,會在§15.2.4.3中進一步討論。

介面的每個基本介面都應是輸出安全的 (§19.2.3.2)。

19.3 介面本體

介面 的interface_body 會定義介面的成員。

interface_body
    : '{' interface_member_declaration* '}'
    ;

19.4 介面成員

19.4.1 一般規定

介面的成員是繼承自基底介面的成員,以及介面本身所宣告的成員。

interface_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | static_constructor_declaration
    | operator_declaration
    | type_declaration
    ;

此子句會以介面的限制來擴充類別 (§15.3) 中成員的描述。 介面成員會使用 member_declarations 宣告,並搭配下列其他規則:

  • 不允許 finalizer_declaration
  • 不允許實例建構函式 constructor_declarations。
  • 所有介面成員都隱含地具有公用存取權;不過,除了靜態建構函式 (§15.12) 之外,允許明確存取修飾詞 (§7.5.2) 。
  • abstract修飾詞是隱含於沒有本文的介面函式成員;該修飾詞可能會明確提供。
  • 除非使用 or virtual 修飾詞,否則sealed其宣告包含本文的介面實例函式成員是隱含private成員。 virtual修飾符可以明確給出。
  • private介面的 或 sealed 函式成員應該具有主體。
  • private函數成員不得具有修飾符 sealed
  • 衍生介面可能會覆寫基底介面中宣告的抽象或虛擬成員。
  • 明確實作的函式成員不應有修飾詞 sealed

某些宣告,例如 constant_declaration§15.4) 在介面中沒有限制。

介面的繼承成員特別不是介面宣告空間的一部分。 因此,允許界面宣告成員,其名稱或簽章可以與繼承的成員相同。 發生這種情況時,會說衍生介面成員會 隱藏 基底介面成員。 隱藏繼承的成員不會被視為錯誤,但會導致警告 (§7.7.2.3) 。

如果在宣告中包含了new修飾詞,而並未隱藏繼承的成員,則會因此發出警告。

注意:嚴格來說,類別 object 中的成員不是任何介面的成員 (§19.4)。 不過,類別中的 object 成員可透過任何介面類型中的成員查閱來取得(§12.5)。 尾註

在多個元件中宣告之介面的成員集合(\15.2.7)是每個元件中宣告的成員聯集。 介面宣告所有部分的主體會共用相同的宣告空間({7.3),而每個成員的範圍({7.7)則延伸到所有元件的主體。

範例:請考慮具有成員IA實作和屬性M的介面P。 實作類型C不會提供 或 M的實作P。 它們必須透過參考來存取,其編譯時間類型是隱含可轉換為 IAIB的介面。 這些成員不會透過對類型 C為 的變數進行成員查閱來找到。

interface IA
{
    public int P { get { return 10; } }
    public void M()
    {
        Console.WriteLine("IA.M");
    }
}

interface IB : IA
{
    public new int P { get { return 20; } }
    void IA.M()
    {
        Console.WriteLine("IB.M");
    }
}

class C : IB { }

class Test
{
    public static void Main()
    {
        C c = new C();
        ((IA)c).M();                               // cast needed
        Console.WriteLine($"IA.P = {((IA)c).P}");  // cast needed
        Console.WriteLine($"IB.P = {((IB)c).P}");  // cast needed
    }
}

在介面 IAIB中,成員 M 可直接依名稱存取。 但是,在方法 Main中,我們不能寫 c.M()c.P,因為這些名稱是不可見的。 若要找到它們,需要轉換至適當的介面類型。 in MIB宣告使用明確的介面實作語法。 這是讓該方法覆寫 中的 IA方法的必要條件;修飾子 override 不得套用至函式成員。 結束範例

19.4.2 介面欄位

此子句會針對介面中宣告的欄位,增強類別 §15.5 中欄位的描述。

介面欄位是使用 field_declarations (§15.5.1) 宣告,並具有下列其他規則:

  • 這是field_declaration宣告實例欄位的編譯時錯誤。

範例:下列程式包含各種類型的靜態成員:

public interface IX
{
    public const int Constant = 100;
    protected static int field;

    static IX()
    {
        Console.WriteLine("static members initialized");
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
        field = 50;
        Console.WriteLine("static constructor has run");
    }
}

public class Test: IX
{
    public static void Main()
    {
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
    }
}

產生的輸出是

static members initialized
constant = 100, field = 0
static constructor has run
constant = 100, field = 50

結束範例

如需靜態欄位配置和初始化的相關資訊,請參閱 §19.4.8

19.4.3 介面方法

此子句會針對介面中宣告的方法,增強類別 §15.6 中方法的描述。

介面方法是使用 method_declaration§15.6) ) 來宣告。 介面方法宣告的 屬性return_typeref_return_type識別碼parameter_list 與類別中方法宣告的意義相同。 介面方法具有下列其他規則:

  • method_modifier 不得包括 override

  • 主體為分號 (;) 的方法為 abstract; abstract 修飾符不是必需的,但允許使用。

  • 具有區塊主體或運算式主體作為 method_body 的介面方法宣告是 virtual; virtual 修飾詞不是必需的,但允許。

  • method_declaration不得有type_parameter_constraints_clause,除非它也有type_parameter_list

  • 針對類別方法陳述的修飾元有效組合的需求清單已擴充,如下所示:

    • 非 extern 的靜態宣告應具有區塊體或運算式體作為 method_body
    • 非 extern 的虛擬宣告應具有區塊體或運算式體作為 method_body
    • 非外部的私人聲明應具有區塊體或表達體作為 method_body
    • 非外部的密封聲明應以塊體或表達體作為 method_body
    • 非同步宣告應該具有區塊主體或運算式主體作為 method_body
  • 介面方法的所有參數類型都應是輸入安全的 (§19.2.3.2) ,傳回類型應該是 void 或輸出安全的。

  • 任何輸出或參考參數類型也應是輸出安全的。

    注意:由於常見的實作限制,輸出參數必須安全輸入。 尾註

  • 方法的任何類型參數上的每個類別類型約束、介面類型約束和類型參數約束都應是輸入安全的。

這些規則可確保介面中任何協變或逆變的使用方式都會維持類型安全。

範例:

interface I<out T>
{
    void M<U>() where U : T;     // Error
}

格式不正確,因為將 T 作為對 U 的類型參數約束的使用並不安全。

如果這項限制不實施,就可能會以下列方式違反類型安全:

interface I<out T>
{
    void M<U>() where U : T;
}
class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<D>() {...} 
}

...

I<B> b = new C();
b.M<E>();

這實際上是對 C.M<E> 的呼叫。 但該呼叫需要 E 衍生自 D,因此會在這裡違反類型安全。

結束範例

附註: 請參閱 §19.4.2 以取得範例,該範例不僅顯示具有實作的靜態方法,而且當呼叫該方法 Main 並具有正確的傳回類型和簽章時,它也是進入點。 尾註

在介面中宣告實作的虛擬方法可能會覆寫為衍生介面中的抽象方法。 這稱為重新抽象。

範例:

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

interface IB: IA
{
    abstract void IA.M();    // reabstraction of M
}

這在衍生介面中很有用,其中方法的實作不合適,而且實作類別應該提供更適當的實作。 結束範例

19.4.4 介面屬性

此子句會針對介面中宣告的屬性,增強類別 §15.7 中的屬性描述。

介面屬性是使用 property_declaration§15.7.1) 宣告,並搭配下列其他規則:

  • property_modifier 不得包括 override

  • 明確的介面成員實作不得包含 accessor_modifier§15.7.3) 。

  • 衍生介面可能會明確實作基底介面中宣告的抽象介面屬性。

    附註: 由於介面不能包含實例欄位,因此介面內容不能是實例自動內容,因為這需要宣告隱含隱藏的實例欄位。 尾註

  • 如果有 get 存取子,介面屬性的類型應為輸出安全,如果有 set 存取子,則為輸入安全。

  • 具有區塊主體或運算式主體作為 method_body 的介面方法宣告是 virtual; virtual 修飾詞不是必需的,但允許。

  • 沒有實作的實例 property_declarationabstract; abstract 修飾子不是必需的,但允許。 它 永遠不會 被視為自動實作的屬性 (§15.7.4) 。

19.4.5 介面事件

此子句會針對介面中宣告的事件,增強類別 §15.8 中事件的描述。

介面事件會使用 event_declaration§15.8.1) 宣告,並具有下列其他規則:

  • event_modifier 不得包括 override
  • 衍生介面可能會實作基底介面中宣告的抽象介面事件 (§15.8.5) 。
  • 這是實例中variable_declarators包含任何variable_initializer event_declaration的編譯時間錯誤。
  • 具有 virtual or sealed 修飾元的實例事件必須宣告存取子。 它 永遠不會 被視為自動實作的類似欄位的事件 (§15.8.2) 。
  • 具有 abstract 修飾詞的實例事件不得宣告存取子。
  • 介面事件的型別應該是輸入安全。

19.4.6 介面索引子

此子句會針對介面中宣告的索引子,增強類別 §15.9 中索引子的描述。

介面索引子是使用 indexer_declarations (§15.9) 宣告,並具有下列其他規則:

  • indexer_modifier 不得包括 override.

  • 具有運算式主體或包含具有區塊主體或運算式主體的存取子的indexer_declarationvirtual;virtual修飾詞不是必要項目,但允許修飾詞。

  • 存取子主體為分號 () 的;abstract;abstract修飾符不是必需的,但允許。

  • 介面索引子的所有參數類型都應該是輸入安全的 (§19.2.3.2) 。

  • 任何輸出或參考參數類型也應是輸出安全的。

    注意:由於常見的實作限制,輸出參數必須安全輸入。 尾註

  • 如果有 get 存取子,介面索引器的型別應為輸出安全,如果有 set 存取子,則為輸入安全。

19.4.7 介面運算子

此子句會針對介面中宣告的運算子,增強類別 §15.10operator_declaration成員的描述。

介面中的 operator_declaration 是實作 (§19.1)。

這是介面宣告轉換、相等或不等式運算子的編譯階段錯誤。

19.4.8 介面靜態建構函式

此子句會針對介面中宣告的靜態建構函式,增強類別 §15.12 中靜態建構函式的描述。

封閉 (§8.4.3) 介面的靜態建構函式在指定的應用程式網域中最多執行一次。 靜態建構函式的執行是由應用程式網域內發生的下列第一個動作所觸發:

  • 會參考介面的任何靜態成員。
  • 在針對包含執行開始的方法 (Main) 的Main介面呼叫方法之前
  • 該介面提供成員的實作,而該實作會作為該成員最具體的實作 (§19.4.10) 進行存取。

附註: 如果上述動作都未發生,則介面的靜態建構函式可能不會針對已建立及使用實作介面類型實例的程式執行。 尾註

若要初始化新的封閉式介面類型,請先為該特定封閉式類型建立一組新的靜態欄位。 每個靜態欄位都會起始設定為其預設值。 接下來,會針對這些靜態欄位執行靜態欄位初始化運算式。 最後,執行靜態建構函式。

注意:如需使用介面內宣告的各種靜態成員 (包括 Main 方法) 的範例,請參閱 §19.4.2尾註

19.4.9 介面巢狀類型

此子句會針對介面中宣告的巢狀類型,增強類別 §15.3.9 中巢狀類型的描述。

在使用 variance_annotation 宣告的類型參數範圍內宣告類別類型、結構類型或列舉類型是錯誤 (§19.2.3.1)。

範例:以下的 C 宣告是錯誤。

interface IOuter<out T>
{
    class C { } // error: class declaration within scope of variant type parameter 'T'
}

結束範例

19.4.10 最具體的實作

每個類別和結構都應該針對該類型在其直接和間接介面中出現的實作中,該類型實作的所有介面中宣告的每個虛擬成員都有最具體的實作。 最具體的實作是唯一的實作,比其他所有實作都更具體。

附註: 最具體的實作規則可確保程式設計師在發生衝突時明確解決鑽石介面繼承所產生的歧義。 尾註

對於實作介面TI2I3的結構或類別類型,I2其中 和 I3 兩者都直接或間接衍生自宣告成員I的介面M,最M具體的實作是:

  • 如果宣告 的T實作,則I.M該實作是最具體的實作。
  • 否則,如果 是類別,且直接或間接基底類別宣告 的T實作,則I.MT衍生的基底類別是最具體的實作。
  • 否則,如果 和 是由I2直接或間接實作I3T衍生自I3的介面,I2I3.M是比 I2.M更具體的實作。
  • 否則,兩者I2.MI3.M不是更具體的,並且會發生錯誤。

範例:

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

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

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

abstract class C: IB, IC { } // error: no most specific implementation for 'IA.M'

abstract class D: IA, IB, IC // OK
{
    public abstract void M();
}

最具體的實作規則可確保程式設計師在衝突發生時明確解決衝突(即鑽石繼承引起的歧義)。 結束範例

19.4.11 介面成員存取

介面成員是透過成員存取 (§12.8.7) 和索引子存取 (§12.8.12.4) 形式的I.MI[A]表達式來存取,其中 I 是介面類型,M是該介面類型的常數、欄位、方法、屬性或事件,而且A是索引子引數清單。

在具有直接或間接基類D的類別B中,B其中直接或間接實現介面II定義方法M(),只有在靜態 (base.M()) 繫結到類別類型中的實現base.M(),表達式M()才有效。

對於嚴格單一繼承的介面 (繼承鏈結中的每個介面都恰好有零個或一個直接基底介面) ,成員查閱 (§12.5) 、方法叫用 (§12.8.10.2) 和索引子存取 (§12.8.12.4) 規則的效果與類別和結構完全相同:更多的衍生成員會隱藏具有相同名稱或簽章的較少衍生成員。 不過,對於多重繼承介面,當兩個或多個不相關的基底介面宣告具有相同名稱或簽章的成員時,可能會發生模棱兩可的情況。 這個子程式示範數個範例,其中有些會導致模棱兩可,有些則不明確。 在所有情況下,明確轉換都可以用來解析模棱兩可。

範例:在下列程式代碼中

interface IList
{
    int Count { get; set; }
}

interface ICounter
{
    int Count { get; set; }
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
        x.Count = 1;             // Error
        ((IList)x).Count = 1;    // Ok, invokes IList.Count.set
        ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count
    }
}

第一個語句會造成編譯時期錯誤,因為在中對Count的成員查找(§12.5)是模棱兩可的。 範例顯示,通過將 x 轉換為適當的基底介面類型,解決了模糊問題。 這類轉換沒有運行時間成本,它們只是在編譯時期將實例檢視為較不衍生的類型所組成。

結束範例

範例:在下列程式代碼中

interface IInteger
{
    void Add(int i);
}

interface IDouble
{
    void Add(double d);
}

interface INumber : IInteger, IDouble {}

class C
{
    void Test(INumber n)
    {
        n.Add(1);             // Invokes IInteger.Add
        n.Add(1.0);           // Only IDouble.Add is applicable
        ((IInteger)n).Add(1); // Only IInteger.Add is a candidate
        ((IDouble)n).Add(1);  // Only IDouble.Add is a candidate
    }
}

n.Add(1)調用會依據IInteger.Add的過載解析規則來選取。 同樣地,叫用 n.Add(1.0) 會選取 IDouble.Add。 當插入明確的轉換時,只有一個可選的方法,因此不會有任何模糊不清。

結束範例

範例:在下列程式代碼中

interface IBase
{
    void F(int i);
}

interface ILeft : IBase
{
    new void F(int i);
}

interface IRight : IBase
{
    void G();
}

interface IDerived : ILeft, IRight {}

class A
{
    void Test(IDerived d)
    {
        d.F(1);           // Invokes ILeft.F
        ((IBase)d).F(1);  // Invokes IBase.F
        ((ILeft)d).F(1);  // Invokes ILeft.F
        ((IRight)d).F(1); // Invokes IBase.F
    }
}

IBase.F成員被ILeft.F成員隱藏。 因此,調用 d.F(1) 會選取 ILeft.F,即使 IBase.F 似乎不會隱藏在透過的 IRight存取路徑中。

隱藏多重繼承介面的直覺規則很簡單:如果某個成員在任何存取路徑中被隱藏,則在所有存取路徑中都會被隱藏。 由於從 IDerivedILeftIBase 的存取路徑隱藏了 IBase.F,因此成員在從 IDerivedIRightIBase 的存取路徑中也被隱藏。

結束範例

19.5 限定介面成員名稱

介面成員有時會被稱為其限定介面成員名稱。 介面成員的限定名稱由宣告該成員的介面名稱加上一個點,然後再加上成員名稱所組成。 成員的限定名稱會參考宣告成員的介面。

範例:給定的宣告

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

的限定名稱 PaintIControl.Paint ,而 SetText 的限定名稱為 ITextBox.SetText。 在上述範例中,無法將 Paint 稱為 ITextBox.Paint

結束範例

當介面是命名空間的一部分時,限定介面成員名稱可以包含命名空間名稱。

範例:

namespace GraphicsLib
{
    interface IPolygon
    {
        void CalculateArea();
    }
}

GraphicsLib 命名空間中,IPolygon.CalculateAreaGraphicsLib.IPolygon.CalculateArea 都是 CalculateArea 方法的限定的介面成員名稱。

結束範例

19.6 介面實作

19.6.1 一般規定

介面可由類別和結構實作。 若要指出類別或結構直接實作介面,介面會包含在類別或結構的基類清單中。

實作介面C的類別或結構I必須為可存取的I每個成員C提供或繼承實作。 的 I 公共成員可以在 的 C公共成員中定義。 宣IC告的非公用成員可以在使用明確介面實作 (C) 中定義。

衍生類型中的成員符合介面對應 (§19.6.5) ,但未實作相符的基底介面成員,會引進新的成員。 當需要明確的介面實作來定義介面成員時,就會發生這種情況。

範例:

interface ICloneable
{
    object Clone();
}

interface IComparable
{
    int CompareTo(object other);
}

class ListEntry : ICloneable, IComparable
{
    public object Clone() {...}    
    public int CompareTo(object other) {...}
}

結束範例

直接實作介面的類別或結構也會隱含實作介面的所有基底介面。 即使類別或結構未明確列出基類清單中的所有基底介面,也是如此。

範例:

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    public void Paint() {...}
    public void SetText(string text) {...}
}

在這裡,類別 TextBox 會同時實作 IControlITextBox

結束範例

當類別 C 直接實作介面時,衍生自 C 的所有類別也會隱含實作 介面。

類別宣告中指定的基底介面可以是建構介面類型 (§8.4§19.2) 。

範例:下列程式代碼說明類別如何實作建構的介面類型:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

結束範例

泛型類別宣告的基底介面應符合 §19.6.3 中所述的唯一性規則。

19.6.2 明確介面成員實作

為了實作介面,類別、結構或介面可以宣告 明確的介面成員實作。 明確的介面成員實作是參考限定介面成員名稱的方法、屬性、事件或索引器宣告。 在基底介面中實作非公用成員的類別或結構必須宣告明確的介面成員實作。 在基底介面中實作成員的介面必須宣告明確的介面成員實作。

滿足介面對應 (§19.6.5) 的衍生介面成員會隱藏基底介面成員 (§7.7.2) 。 除非修飾符存在,否則 new 編譯器應該發出警告。

範例:

interface IList<T>
{
    T[] GetElements();
}

interface IDictionary<K, V>
{
    V this[K key] { get; }
    void Add(K key, V value);
}

class List<T> : IList<T>, IDictionary<int, T>
{
    public T[] GetElements() {...}
    T IDictionary<int, T>.this[int index] {...}
    void IDictionary<int, T>.Add(int index, T value) {...}
}

這裡 IDictionary<int,T>.thisIDictionary<int,T>.Add 是明確的介面成員實作。

結束範例

範例:在某些情況下,介面成員的名稱可能不適合實作類別,在此情況下,介面成員可以使用明確的介面成員實作。 例如,實作檔案抽象的類別可能會實作一個具有釋放檔案資源效果的成員函式,並使用明確介面成員實作來實作 Close 介面中的 Dispose 方法。

interface IDisposable
{
    void Dispose();
}

class MyFile : IDisposable
{
    void IDisposable.Dispose() => Close();

    public void Close()
    {
        // Do what's necessary to close the file
        System.GC.SuppressFinalize(this);
    }
}

結束範例

在方法調用、屬性存取、事件存取或索引器存取中,無法透過其限定介面成員名稱存取明確介面成員實作。 明確的介面實例成員實作只能透過介面實例存取,而且在此情況下,只會由其成員名稱參考。 明確介面靜態成員實作只能透過介面名稱來存取。

明確介面成員實作若包含任何修飾詞(例如 extern)以外的async,便會產生編譯時期錯誤。

明確的介面方法實作會從 介面繼承任何類型參數條件約束。

明確介面方法實作中的type_parameter_constraints_clause只能包括classstructprimary_constraint,適用於根據繼承約束已知為參考或實值型別的type_parameter。 在明確介面方法的實作簽章中,格式 T? 的任何類型,其中 T 是類型參數,解譯如下:

  • 如果為 class 類型參數 T 加入條件約束,則 T? 為可為 Null 的參考型別,否則為
  • 如果沒有新增條件約束,或已新增struct條件約束,則對於類型參數TT?會是可為 Null 的實值型別。

範例:下列示範在涉及類型參數時規則的運作方式:

#nullable enable
interface I
{
    void Foo<T>(T? value) where T : class;
    void Foo<T>(T? value) where T : struct;
}

class C : I
{
    void I.Foo<T>(T? value) where T : class { }
    void I.Foo<T>(T? value) where T : struct { }
}

如果沒有類型參數條件約束 where T : class,就無法覆寫具有參考型別型別參數的基底方法。 結束範例

注意:明確介面成員實作的可見性屬性與其他成員不同。 因為明確介面成員實作永遠無法透過方法調用或屬性存取中的限定介面成員名稱來存取,所以它們從某種意義上說是私用的。 不過,由於可以透過介面存取它們,因此在某種意義上,它們也和宣告它們的介面一樣是公共的。 明確介面成員實作有兩個主要用途:

  • 由於無法透過類別或結構實例存取明確介面成員實作,因此允許將介面實作從類別或結構的公用介面中排除。 當類別或結構實作對該類別或結構取用者不感興趣的內部介面時,這特別有用。
  • 明確介面成員實作允許釐清具有相同簽章的介面成員。 如果沒有明確的介面成員實作,類別、結構或介面就不可能具有相同的簽章和傳回類型的介面成員的不同實作,就像類別、結構或介面不可能在具有相同簽章但傳回類型不同的介面成員上有任何實作一樣。

尾註

若要讓明確介面成員實作有效,類別、結構或介面應該在其基類或基底介面清單中命名介面,其中包含其完整介面成員名稱、類型、類型參數數目和參數類型完全符合明確介面成員實作的成員。 如果介面函式成員具有參數陣列,則允許相關聯明確介面成員實作的對應參數具有 params 修飾詞,但並非必須。 如果介面函式成員沒有參數數位,則相關聯的明確介面成員實作不應該有參數陣列。

範例:因此,在下列類別中

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
    int IComparable.CompareTo(object other) {...} // invalid
}

IComparable.CompareTo 的宣告會導致編譯時錯誤,因為 IComparable 未列在 Shape 的基類清單中,且不是 ICloneable 的基底介面。 同樣地,在宣告中

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
}

class Ellipse : Shape
{
    object ICloneable.Clone() {...} // invalid
}

ICloneable.Clone 中的 Ellipse 的宣告會導致編譯時期錯誤,因為 ICloneable 未明確列在 Ellipse 的基類清單中。

結束範例

明確介面成員實作的限定介面成員名稱應該參考宣告成員的介面。

範例:因此,在這些宣告中

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
}

Paint 的明確介面成員實作必須寫入為 IControl.Paint,而不是 ITextBox.Paint

結束範例

19.6.3 實作介面的唯一性

泛型型別宣告所實作的介面對於所有可能的建構型別而言,都應該是唯一的。 如果沒有此規則,就無法判斷針對特定建構型別呼叫的正確方法。

範例:假設允許撰寫泛型類別宣告,如下所示:

interface I<T>
{
    void F();
}

class X<U ,V> : I<U>, I<V> // Error: I<U> and I<V> conflict
{
    void I<U>.F() {...}
    void I<V>.F() {...}
}

如果允許,則無法判斷在下列案例中要執行的程式代碼:

I<int> x = new X<int, int>();
x.F();

結束範例

若要判斷泛型型別宣告的介面清單是否有效,請執行下列步驟:

  • 讓我們 L 成為直接在泛型類別、結構或介面宣告 C中指定的介面清單。
  • 新增在L中的介面之任何基底介面至L
  • L 移除任何重複項目。
  • 如果從 C 建立的任何可能建構型別,在型別參數被替換為 L 之後,會導致 L 中的兩個介面相同,則 C 的宣告無效。 判斷所有可能的建構型別時,不會考慮條件約束宣告。

注意:在上述類別宣告 X 中,介面清單 L 包含 l<U>I<V>。 宣告無效,因為具有 UV 相同型別的任何建構型別都會導致這兩個介面是相同的型別。 尾註

在不同繼承層級中定義的介面可以統一:

interface I<T>
{
    void F();
}

class Base<U> : I<U>
{
    void I<U>.F() {...}
}

class Derived<U, V> : Base<U>, I<V> // Ok
{
    void I<V>.F() {...}
}

即使 Derived<U,V> 同時實作 I<U>I<V>,此程序代碼也是有效的。 程式碼

I<int> x = new Derived<int, int>();
x.F();

叫用 中 Derived的方法,因為 Derived<int,int>' 有效地 I<int> 重新實作 (§19.6.7)。

19.6.4 通用方法的實現

當泛型方法隱含實作介面方法時,每個方法類型參數所指定的條件約束在兩個宣告中都應該相等(在任何介面類型參數取代為適當的類型自變數之後),其中方法類型參數是由序數位置識別,由左至右。

範例:在下列程式代碼中:

interface I<X, Y, Z>
{
    void F<T>(T t) where T : X;
    void G<T>(T t) where T : Y;
    void H<T>(T t) where T : Z;
}

class C : I<object, C, string>
{
    public void F<T>(T t) {...}                  // Ok
    public void G<T>(T t) where T : C {...}      // Ok
    public void H<T>(T t) where T : string {...} // Error
}

方法 C.F<T> 會隱含實作 I<object,C,string>.F<T>。 在此情況下,不需要 (也不允許) 指定條件約束C.F<T>T: object因為 object 是所有類型參數的隱含條件約束。 方法 C.G<T> 會隱含實作 I<object,C,string>.G<T> ,因為條件約束會比對接口中的條件約束,在介面類型參數取代為對應的類型自變數之後。 方法 C.H<T> 的條件約束是錯誤,因為密封類型 (string 在此案例中) 無法當做條件約束使用。 省略條件約束也是錯誤,因為需要隱含介面方法實作的條件約束才能比對。 因此,無法隱式實作I<object,C,string>.H<T>。 這個介面方法只能使用明確的介面成員實作來實作:

class C : I<object, C, string>
{
    ...
    public void H<U>(U u) where U : class {...}

    void I<object, C, string>.H<T>(T t)
    {
        string s = t; // Ok
        H<T>(t);
    }
}

在此情況下,界面成員的明確實作會調用一個具有較弱限制條件的公共方法。 從 t 到 s 的指派是有效的,因為 T 繼承了 的條件約束 T: string,即使原始程式碼中無法表示此條件約束也一樣。 結束範例

附註: 當泛型方法明確實作介面方法時,實作方法不允許條件約束 (§15.7.1§19.6.2) 。 尾註

19.6.5 介面映射

類別或結構應該提供類別或結構的基底類別清單中列出之介面之所有抽象成員的實作。 在實作類別或結構中尋找介面成員實作的程序稱為介面對應

類別或結構的 C 介面對應會找出 基類清單中指定之每個介面成員的 C實作。 特定介面成員 I.M的實作 ,其中 I 是宣告成員 M 的介面,是透過檢查每個類別、介面或結構 S來決定,從 開始 C ,並針對每個連續的基類和實作的介面 C重複,直到找到相符項:

  • 如果 S 包含符合 IM的明確介面成員實作宣告,則這個成員是的實作 I.M
  • 否則,如果 S 包含符合 M的非靜態公用成員宣告,則這個成員是的實作 I.M。 如果有多個成員相符,則未指定哪個成員實作 `I.M`。 只有當 S 是建構型別時,泛型型別中宣告的兩個成員有不同的簽章,但類型自變數會使其簽章相同時,才會發生這個情況。

如果實作無法找到 在基類清單中 C指定之所有介面的所有成員,就會發生編譯時期錯誤。 介面的成員包含繼承自基底介面的成員。

建構介面類型的成員將被視為,以對應的類型引數取代任何類型參數,如§15.3.3中所指定。

範例:例如,假設有泛型介面宣告:

interface I<T>
{
    T F(int x, T[,] y);
    T this[int y] { get; }
}

建構的介面 I<string[]> 具有成員:

string[] F(int x, string[,][] y);
string[] this[int y] { get; }

結束範例

為了介面對應,類別、介面或結構成員 A 會在下列情況下比對介面成員 B

  • AB 是方法,且 和的名稱、類型和參數清單AB相同。
  • AB 是屬性,AB 的名稱和類型相同,A 具有與 B 相同的存取子(如果 A 不是明確的介面成員實作,則允許有其他存取子)。
  • AB 是事件,且 和 的名稱和類型AB相同。
  • AB是索引器,AB的類型和參數清單相同,而且A具有與B相同的存取子(A如果不是明確的介面成員實作,則可以有其他存取子)。

介面對應演算法的顯著影響如下:

  • 明確介面成員實作在判斷實作介面成員的類別或結構成員時,優先於相同類別或結構中的其他成員。
  • 非公用成員和靜態成員都不會參與介面對應。

範例:在下列程式代碼中

interface ICloneable
{
    object Clone();
}

class C : ICloneable
{
    object ICloneable.Clone() {...}
    public object Clone() {...}
}

ICloneable.CloneC 成員會成為 CloneICloneable 的實作,因為明確的介面成員實作優先於其他成員。

結束範例

如果類別或結構實作兩個或多個介面,其中包含具有相同名稱、類型和參數類型的成員,則可以將每個介面成員對應至單一類別或結構成員。

範例:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

class Page : IControl, IForm
{
    public void Paint() {...}
}

在這裡,PaintIControl 的方法都被映射到 IForm 中的 Paint 方法。 當然,也可以針對這兩種方法有個別的明確介面成員實作。

結束範例

如果類別或結構實作包含隱藏成員的介面,則某些成員可能需要透過明確的介面成員實作來實作。

範例:

interface IBase
{
    int P { get; }
}

interface IDerived : IBase
{
    new int P();
}

此介面的實作至少需要一個明確的介面成員實作,而且會採用下列其中一種形式

class C1 : IDerived
{
    int IBase.P { get; }
    int IDerived.P() {...}
}
class C2 : IDerived
{
    public int P { get; }
    int IDerived.P() {...}
}
class C3 : IDerived
{
    int IBase.P { get; }
    public int P() {...}
}

結束範例

當類別實作多個具有相同基底介面的介面時,基底介面只能有一個實作。

範例:在下列程式代碼中

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

class ComboBox : IControl, ITextBox, IListBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
    void IListBox.SetItems(string[] items) {...}
}

在基類清單中,IControl、由 IControl 繼承的 ITextBox 和由 IControl 繼承的 IListBox 都不可能有單獨的實作。 事實上,這些介面沒有個別身分識別的概念。 相反地,ITextBoxIListBox的實作共用相同的IControl,而ComboBox則被視為實作了三個介面:IControlITextBoxIListBox

結束範例

基類的成員會參與介面映射。

範例:在下列程式代碼中

interface Interface1
{
    void F();
}

class Class1
{
    public void F() {}
    public void G() {}
}

class Class2 : Class1, Interface1
{
    public new void G() {}
}

方法 FClass1 中被用於 Class2'sInterface1 實作。

結束範例

19.6.6 介面實作繼承

類別會繼承其基類所提供的所有介面實作。

若未明確重新實作介面,衍生類別就無法以任何方式改變其繼承自基類的介面對應。

範例:在宣告中

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public void Paint() {...}
}

class TextBox : Control
{
    public new void Paint() {...}
}

Paint中的TextBox方法會隱藏Paint中的Control方法,但不會改變Control.PaintIControl.Paint的對應。透過類別實例和介面實例呼叫Paint時,將會產生下列效果。

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();

結束範例

不過,當介面方法對應至類別中的虛擬方法時,衍生類別可以覆寫虛擬方法並改變介面的實作。

範例:將上述宣告重寫為

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public virtual void Paint() {...}
}

class TextBox : Control
{
    public override void Paint() {...}
}

現在將觀察到下列效果

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes TextBox.Paint();

結束範例

由於明確的介面成員實作無法宣告為虛擬,因此無法覆寫明確的介面成員實作。 不過,明確介面成員實作呼叫另一個方法是完全有效的,而且其他方法可以宣告為虛擬,以允許衍生類別覆寫它。

範例:

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() { PaintControl(); }
    protected virtual void PaintControl() {...}
}

class TextBox : Control
{
    protected override void PaintControl() {...}
}

在這裡,衍生自Control的類別可以藉由覆寫IControl.Paint方法來特製化PaintControl的實作。

結束範例

19.6.7 介面重新實作

繼承介面實作的類別允許藉由將介面包含在基類清單中來重新實現該介面。

介面的重新實作會遵循與介面初始實作完全相同的介面對應規則。 因此,繼承的介面對應對為重新實作介面所建立的介面對應沒有任何作用。

範例:在宣告中

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() {...}
}

class MyControl : Control, IControl
{
    public void Paint() {}
}

Control IControl.Paint對應至Control.IControl.Paint的事實不會影響MyControl中的重新實作,此實作會對應IControl.PaintMyControl.Paint

結束範例

繼承的公用成員宣告和繼承的明確介面成員宣告會參與重新實作介面的介面映射過程。

範例:

interface IMethods
{
    void F();
    void G();
    void H();
    void I();
}

class Base : IMethods
{
    void IMethods.F() {}
    void IMethods.G() {}
    public void H() {}
    public void I() {}
}

class Derived : Base, IMethods
{
    public void F() {}
    void IMethods.H() {}
}

在這裡,IMethodsDerived 中的實作會將介面方法對應至 Derived.FBase.IMethods.GDerived.IMethods.HBase.I

結束範例

當類別實作介面時,它也會隱含地實作該介面的所有基底介面。 同樣地,介面的重新實作也會隱含地重新實作所有介面的基底介面。

範例:

interface IBase
{
    void F();
}

interface IDerived : IBase
{
    void G();
}

class C : IDerived
{
    void IBase.F() {...}
    void IDerived.G() {...}
}

class D : C, IDerived
{
    public void F() {...}
    public void G() {...}
}

在這裡,重新實作 IDerived 也會重新實作 IBase,並對應 IBase.FD.F

結束範例

19.6.8 抽象類別和介面

與非抽象類一樣,抽象類應提供類別基類清單中列出的介面的所有抽象成員的實作。 不過,抽象類允許將介面方法映射至抽象方法。

範例:

interface IMethods
{
    void F();
    void G();
}

abstract class C : IMethods
{
    public abstract void F();
    public abstract void G();
    }

在這裡,IMethodsF 的實作被映射到抽象方法上,這些方法應在從 G 衍生的非抽象類別中被覆寫。

結束範例

明確介面成員實作不可以是抽象的,但明確介面成員實作當然可以呼叫抽象方法。

範例:

interface IMethods
{
    void F();
    void G();
}

abstract class C: IMethods
{
    void IMethods.F() { FF(); }
    void IMethods.G() { GG(); }
    protected abstract void FF();
    protected abstract void GG();
}

在這裡,衍生自 C 的非抽象類別需要覆寫 FFGG,從而提供 IMethods 的實際實作。

結束範例