18 個介面
18.1 一般
介面會定義合約。 實作介面的類別或結構應遵守其合約。 介面可能繼承自多個基底介面,而類別或結構可能會實作多個介面。
介面可以包含方法、屬性、事件和索引器。 介面本身不會為其宣告的成員提供實作。 介面只會指定實作 介面的類別或結構所提供的成員。
18.2 介面宣告
18.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包含一組選擇性的屬性 (~22),後面接著一組選擇性的interface_modifier s (#18.2.2),後面接著選擇性部分修飾詞 (\15.2.7),後面接著關鍵詞和標識符來命名介面,後面接著選擇性的variant_type_parameter_list規格 (interface
),後面接著選擇性interface_basespecification ({18.2.4),後面接著選擇性的type_parameter_constraints_clause規格({15.2.5),後面接著interface_body ({18.3),選擇性地後面接著分號。
除非介面宣告也提供variant_type_parameter_list,否則介面宣告不得提供type_parameter_constraints_clause。
提供 variant_type_parameter_list 的介面宣告是泛型介面宣告。 此外,泛型類別宣告或泛型結構宣告內巢狀的任何介面本身都是泛型介面宣告,因為應該提供包含型別的型別自變數來建立建構型別 ({8.4)。
18.2.2 介面修飾詞
interface_declaration可以選擇性地包含一連串的介面修飾詞:
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (•23.2) 僅適用於不安全的程式代碼 (~23)。
同一個修飾詞在介面宣告中出現多次是編譯時期錯誤。
new
只有在類別內定義的介面上才允許修飾詞。 它會指定介面會以相同名稱隱藏繼承的成員,如 \15.3.5 中所述。
public
、 protected
internal
和 private
修飾詞可控制 介面的存取範圍。 視介面宣告發生的內容而定,可能只允許其中一些修飾詞 ({7.5.2)。 當部分類型宣告 ({15.2.7) 包含輔助功能規格時(透過public
、protected
、 internal
和 private
修飾詞),就會套用 \15.2.2 中的規則。
18.2.3 Variant 類型參數清單
18.2.3.1 一般
Variant 類型參數清單只能在介面和委派類型上發生。 與一 般type_parameter_list的差異在於每個類型參數上的選擇性 variance_annotation 。
variant_type_parameter_list
: '<' variant_type_parameters '>'
;
variant_type_parameters
: attributes? variance_annotation? type_parameter
| variant_type_parameters ',' 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
為不變異。end 範例
如果泛型介面在多個元件中宣告 ({15.2.3),則每個部分宣告都應該為每個類型參數指定相同的變異數。
18.2.3.2 差異安全性
類型類型參數清單中的變異數批注出現會限制類型宣告中可以發生類型的位置。
如果下列其中一項保留,類型 T 為輸出不安全:
-
T
是反變數類型參數 -
T
是具有 output-unsafe 元素類型的數位類型 -
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,則為輸入安全。
18.2.3.3 變異數轉換
變異數註釋的目的是提供更寬大(但仍類型安全)轉換成介面和委派類型。 為此,隱含轉換(~10.2)和明確轉換的定義(•10.3)會利用變異數轉換的概念,其定義如下:
如果 T<Aᵢ, ..., Aᵥ>
類型是使用 variant 類型參數T<Bᵢ, ..., Bᵥ>
宣告的介面或委派類型,且每個 Variant 類型參數T
都保留下列其中一項,則類型T<Xᵢ, ..., Xᵥ>
可轉換成類型Xᵢ
:
-
Xᵢ
是 covariant,而且有從Aᵢ
到 的隱含參考或身分識別轉換Bᵢ
-
Xᵢ
是反變數,而且有從Bᵢ
到的隱含參考或身分識別轉換Aᵢ
-
Xᵢ
是不變的,且身分識別轉換存在從Aᵢ
到Bᵢ
18.2.4 基底介面
介面可以繼承自零個或多個介面類型,這些類型稱為 介面的明確基底介面 。 當介面有一或多個明確的基底介面時,在該介面的宣告中,介面標識符後面接著冒號和基底介面類型的逗號分隔清單。
interface_base
: ':' interface_type_list
;
明確基底介面可以建構介面類型 (~8.4, •18.2)。 基底介面本身不能是型別參數,不過它可以牽涉到範圍中的型別參數。
針對建構的介面類型,明確基底介面的形成方式是在泛型型別宣告上採用明確的基底介面宣告,並以 基底介面宣告中的每個type_parameter 取代,也就是建構型別的對應 type_argument 。
介面的明確基底介面應至少可以和介面本身一樣可存取(~7.5.5)。
注意:例如,在介面的interface_base
internal
指定 或public
介面是編譯時期錯誤。 end note
這是介面直接或間接繼承自本身的編譯時間錯誤。
介面 的基底介面 是明確的基底介面及其基底介面。 換句話說,基底介面集是明確基底介面、其明確基底介面等等的完整可轉移關閉。 介面會繼承其基底介面的所有成員。
範例:在下列程式代碼中
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}
基底介面
IComboBox
為IControl
、ITextBox
與IListBox
。 換句話說,IComboBox
上述介面會繼承成員SetText
以及SetItems
Paint
。end 範例
繼承自建構泛型型別的成員會在型別替代之後繼承。 也就是說,成員中的任何組成型別都有基類宣告的類型參數,以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[,]
會繼承 方法。end 範例
實作介面的類別或結構也會隱含實作介面的所有基底介面。
在部分介面宣告 ({15.2.7) 的多個部分上處理介面,會在 {15.2.4.3 中進一步討論。
介面的每個基底介面都應該是輸出安全(~18.2.3.2)。
18.3 介面主體
介面 的interface_body 會定義介面的成員。
interface_body
: '{' interface_member_declaration* '}'
;
18.4 介面成員
18.4.1 一般
介面的成員是繼承自基底介面的成員,以及介面本身所宣告的成員。
interface_member_declaration
: interface_method_declaration
| interface_property_declaration
| interface_event_declaration
| interface_indexer_declaration
;
介面宣告會宣告零個或多個成員。 介面的成員應該是方法、屬性、事件或索引器。 介面不能包含常數、字段、運算子、實例建構函式、完成項或類型,介面也不能包含任何類型的靜態成員。
所有介面成員都會隱含地具有公用存取權。 這是介面成員宣告包含任何修飾詞的編譯時期錯誤。
interface_declaration會建立新的宣告空間 ({7.3),而且interface_declaration立即包含的類型參數和interface_member_declaration會將新成員引入此宣告空間。 下列規則適用於 interface_member_declarations:
- 介面宣告variant_type_parameter_list中型別參數的名稱應該與相同variant_type_parameter_list中所有其他類型參數的名稱不同,而且與介面的所有成員名稱不同。
- 方法的名稱應該與相同介面中宣告的所有屬性和事件名稱不同。 此外,方法的簽章 ({7.6) 應該與相同介面中宣告之所有其他方法的簽章不同,而相同介面中宣告的兩個方法不得具有與 、
in
和out
完全ref
不同的簽章。 - 屬性或事件的名稱應該與相同介面中宣告的所有其他成員名稱不同。
- 索引器的簽章應該與相同介面中宣告之所有其他索引器的簽章不同。
介面的繼承成員特別不是介面宣告空間的一部分。 因此,允許介面宣告與繼承成員具有相同名稱或簽章的成員。 發生這種情況時,會說衍生介面成員會 隱藏 基底介面成員。 隱藏繼承的成員不會被視為錯誤,但它確實會導致編譯程式發出警告。 若要隱藏警告,衍生介面成員的宣告應包含 new
修飾詞,指出衍生成員是要隱藏基底成員。 本主題會在 \7.7.2.3 中進一步討論。
new
如果修飾詞包含在未隱藏繼承成員的宣告中,則會發出該效果的警告。 拿掉 修飾詞會隱藏 new
此警告。
注意:類別
object
中的成員不是任何介面的成員(~18.4)。 不過,類別中的object
成員可透過任何介面類型中的成員查閱取得(~12.5)。 end note
在多個元件中宣告之介面的成員集合(\15.2.7)是每個元件中宣告的成員聯集。 介面宣告所有部分的主體會共用相同的宣告空間({7.3),而每個成員的範圍({7.7)則延伸到所有元件的主體。
18.4.2 介面方法
介面方法是使用 interface_method_declarations 宣告:
interface_method_declaration
: attributes? 'new'? return_type interface_method_header
| attributes? 'new'? ref_kind ref_return_type interface_method_header
;
interface_method_header
: identifier '(' parameter_list? ')' ';'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
介面方法宣告的屬性、return_type、ref_return_type、標識符和parameter_list與類別中方法宣告的屬性意義相同({15.6)。 不允許介面方法宣告指定方法主體,因此宣告一律以分號結尾。
介面方法的所有參數類型都應該是輸入安全(~18.2.3.2),而且傳回類型應該是 void
或輸出安全。 此外,任何輸出或參考參數類型也應該是輸出安全。
注意:由於常見的實作限制,輸出參數必須安全輸入。 end note
此外,方法之任何類型參數上的每個類別類型條件約束、介面類型條件約束和型別參數條件約束都應該是輸入安全。
此外,方法之任何類型參數上的每個類別類型條件約束、介面類型條件約束和類型參數條件約束都應該是輸入安全。
這些規則可確保介面的任何共變數或反變數使用方式都會維持類型。
範例:
interface I<out T> { void M<U>() where U : T; // Error }
格式不正確,因為 上的
T
做U
為類型參數條件約束的使用不是輸入安全。如果這項限制未就緒,可能會以下列方式違反類型安全:
class B {} class D : B {} class E : B {} class C : I<D> { public void M<U>() {...} } ... I<B> b = new C(); b.M<E>();
這實際上是對的
C.M<E>
呼叫。 但該呼叫需要E
衍生自D
,因此會在這裡違反類型安全。end 範例
18.4.3 介面屬性
介面屬性是使用 interface_property_declaration來宣告:
interface_property_declaration
: attributes? 'new'? type identifier '{' interface_accessors '}'
| attributes? 'new'? ref_kind type identifier '{' ref_interface_accessor '}'
;
interface_accessors
: attributes? 'get' ';'
| attributes? 'set' ';'
| attributes? 'get' ';' attributes? 'set' ';'
| attributes? 'set' ';' attributes? 'get' ';'
;
ref_interface_accessor
: attributes? 'get' ';'
;
介面屬性宣告的屬性、類型和標識碼與類別中屬性宣告的屬性、類型和標識碼具有相同的意義({15.7)。
介面屬性宣告的存取子會對應至類別屬性宣告 ({15.7.3) 的存取子,但 accessor_body 一律為分號。 因此,存取子只會指出屬性是讀寫、只讀還是唯寫。
如果有 get 存取子,介面屬性的類型應為輸出安全,如果有 set 存取子,則為輸入安全。
18.4.4 介面事件
介面事件是使用 interface_event_declaration來宣告:
interface_event_declaration
: attributes? 'new'? 'event' type identifier ';'
;
介面事件宣告的屬性、類型和標識碼與類別中事件宣告的屬性、類型和標識符意義相同({15.8)。
介面事件的型別應該是輸入安全。
18.4.5 介面索引器
介面索引器是使用 interface_indexer_declaration來宣告:
interface_indexer_declaration
: attributes? 'new'? type 'this' '[' parameter_list ']'
'{' interface_accessors '}'
| attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
'{' ref_interface_accessor '}'
;
介面索引器宣告的屬性、型別和parameter_list與類別中索引器宣告的屬性、類型和parameter_list意義相同。
介面索引器宣告的存取子會對應至類別索引器宣告的存取子 ({15.9),不同之處在於 accessor_body 一律為分號。 因此,存取子只會指出索引器是讀寫、只讀還是唯寫。
介面索引器的所有參數類型都應該是輸入安全(~18.2.3.2)。 此外,任何輸出或參考參數類型也應該是輸出安全。
注意:由於常見的實作限制,輸出參數必須安全輸入。 end note
如果有 get 存取子,介面索引器的型別應為輸出安全,如果有 set 存取子,則為輸入安全。
18.4.6 介面成員存取
介面成員是透過成員存取(§12.8.7)和索引器存取(§12.8.12.3)的形式,I.M
和 I[A]
表達式來存取,其中 I
是介面類型,M
為該介面類型的方法、屬性或事件,而 A
是索引器參數清單。
對於嚴格單一繼承的介面(繼承鏈結中的每個介面都只有零或一個直接基底介面),成員查閱的效果(\12.5),方法調用 (12.8.10. 2), 和索引器存取 (•12.8.12.3) 規則與類別和結構完全相同:更多衍生成員隱藏的衍生成員與同名或簽章的衍生成員完全相同。 不過,對於多重繼承介面,當兩個或多個不相關的基底介面宣告具有相同名稱或簽章的成員時,可能會發生模棱兩可的情況。 這個子程式示範數個範例,其中有些會導致模棱兩可,有些則不明確。 在所有情況下,明確轉換都可以用來解析模棱兩可。
範例:在下列程式代碼中
interface IList { int Count { get; set; } } interface ICounter { void Count(int i); } interface IListCounter : IList, ICounter {} class C { void Test(IListCounter x) { x.Count(1); // Error x.Count = 1; // Error ((IList)x).Count = 1; // Ok, invokes IList.Count.set ((ICounter)x).Count(1); // Ok, invokes ICounter.Count } }
前兩個語句會造成編譯時間錯誤,因為 中的成員查閱 (
Count
)IListCounter
模棱兩可。 如範例所示,模棱兩可的解析方式是轉換成x
適當的基底介面類型。 這類轉換沒有運行時間成本,它們只是在編譯時期將實例檢視為較不衍生的類型所組成。end 範例
範例:在下列程式代碼中
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)
叫用會套用 \12.6.4IInteger.Add
載解析規則來選取 。 同樣地,叫用n.Add(1.0)
會選取IDouble.Add
。 插入明確轉換時,只有一個候選方法,因此沒有模棱兩可。end 範例
範例:在下列程式代碼中
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
存取路徑中。隱藏多重繼承介面的直覺規則很簡單:如果成員隱藏在任何存取路徑中,則會隱藏在所有存取路徑中。 由於 從到
IDerived
的存取路徑ILeft
會IBase
隱藏 ,因此成員也會隱藏在存取路徑中,從IBase.F
到IDerived
IRight
。IBase
end 範例
18.5 限定介面成員名稱
介面成員有時會透過其 限定介面成員名稱來參考。 介面成員的限定名稱是由宣告成員的介面名稱所組成,後面接著一個點,後面接著成員的名稱。 成員的限定名稱會參考宣告成員的介面。
範例:指定宣告
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); }
的限定名稱
Paint
為IControl.Paint
,而 SetText 的限定名稱為ITextBox.SetText
。 在上述範例中,無法將 參考Paint
為ITextBox.Paint
。end 範例
當介面是命名空間的一部分時,限定介面成員名稱可以包含命名空間名稱。
範例:
namespace System { public interface ICloneable { object Clone(); } }
在命名空間中
System
,ICloneable.Clone
和System.ICloneable.Clone
都是方法的限定介面成員名稱Clone
。end 範例
18.6 介面實作
18.6.1 一般
介面可由類別和結構實作。 若要指出類別或結構直接實作介面,介面會包含在類別或結構的基類清單中。
範例:
interface ICloneable { object Clone(); } interface IComparable { int CompareTo(object other); } class ListEntry : ICloneable, IComparable { public object Clone() {...} public int CompareTo(object other) {...} }
end 範例
直接實作介面的類別或結構也會隱含實作介面的所有基底介面。 即使類別或結構未明確列出基類清單中的所有基底介面,也是如此。
範例:
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { public void Paint() {...} public void SetText(string text) {...} }
在這裡,類別
TextBox
會同時實作IControl
和ITextBox
。end 範例
當類別 C
直接實作介面時,衍生自 C
的所有類別也會隱含實作 介面。
類別宣告中指定的基底介面可以建構介面類型 ({8.4, {18.2) 。
範例:下列程式代碼說明類別如何實作建構的介面類型:
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}
end 範例
泛型類別宣告的基底介面應滿足 {18.6.3 中所述的唯一性規則。
18.6.2 明確介面成員實作
為了實作介面,類別或結構可以宣告 明確的介面成員實作。 明確的介面成員實作是參考限定介面成員名稱的方法、屬性、事件或索引器宣告。
範例:
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>.this
和IDictionary<int,T>.Add
是明確的介面成員實作。end 範例
範例:在某些情況下,介面成員的名稱可能不適合實作類別,在此情況下,介面成員可以使用明確的介面成員實作。 例如,實作檔案抽象的類別可能會實
Close
作具有釋放檔案資源效果的成員函式,並使用Dispose
明確介面成員實作的IDisposable
介面方法: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); } }
end 範例
在方法調用、屬性存取、事件存取或索引器存取中,無法透過其限定介面成員名稱存取明確介面成員實作。 明確介面成員實作只能透過介面實例存取,而且在該案例中,只能由其成員名稱參考。
明確介面成員實作包含 或 以外的extern
任何修飾詞 (async
) 是編譯時期錯誤。
這是明確介面方法實 作包含 type_parameter_constraints_clause的編譯時間錯誤。 泛型明確介面方法實作的條件約束繼承自介面方法。
注意:明確介面成員實作的輔助功能特性與其他成員不同。 因為明確介面成員實作永遠無法透過方法調用或屬性存取中的限定介面成員名稱來存取,所以它們從某種意義上說是私用的。 不過,由於可以透過介面存取它們,因此其意義也如同宣告它們的介面一樣公用。 明確介面成員實作有兩個主要用途:
- 由於無法透過類別或結構實例存取明確介面成員實作,因此允許將介面實作從類別或結構的公用介面中排除。 當類別或結構實作對該類別或結構取用者不感興趣的內部介面時,這特別有用。
- 明確介面成員實作允許釐清具有相同簽章的介面成員。 如果沒有明確的介面成員實作,類別或結構不可能擁有具有相同簽章和傳回型別之介面成員的不同實作,因為類別或結構不可能擁有所有具有相同簽章但具有不同傳回型別的介面成員的任何實作。
end note
若要讓明確介面成員實作有效,類別或結構應該在其基類清單中命名介面,其中包含限定介面成員名稱、類型、類型、類型、類型參數數目和參數類型完全符合明確介面成員實作的成員。 如果介面函式成員具有參數數位,則允許相關聯明確介面成員實作的對應參數,但不需要具有 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
基類清單中。end 範例
明確介面成員實作的限定介面成員名稱應該參考宣告成員的介面。
範例:因此,在宣告中
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
。end 範例
18.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();
end 範例
若要判斷泛型型別宣告的介面清單是否有效,請執行下列步驟:
- 讓我們
L
成為直接在泛型類別、結構或介面宣告C
中指定的介面清單。 - 將 新增至
L
已在 中L
介面的任何基底介面。 - 從
L
移除任何重複專案。 - 如果從
C
建立的任何可能建構型別,在型別自變數取代為L
之後,會導致 中的L
兩個介面相同,則的C
宣告無效。 判斷所有可能的建構型別時,不會考慮條件約束宣告。
注意:在上述類別宣告
X
中,介面清單L
包含l<U>
和I<V>
。 宣告無效,因為具有U
和V
相同型別的任何建構型別都會導致這兩個介面是相同的型別。 end note
在不同繼承層級指定的介面可以統一:
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>
(~18.6.7)。
18.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
,即使原始程式碼中無法表示此條件約束也一樣。 end 範例
注意:當泛型方法明確實作介面方法時,實作方法上不允許任何條件約束 (\15.7.1, \18.6.2)。 end note
18.6.5 介面對應
類別或結構應提供類別或結構之基類清單中所列介面之所有成員的實作。 在實作類別或結構中尋找介面成員實作的程序稱為介面對應。
類別或結構的 C
介面對應會找出 基類清單中指定之每個介面成員的 C
實作。 特定介面成員 I.M
的實作,其中 I
是宣告成員 M
的介面,是藉由檢查每個類別或結構 S
來判斷,從 開始 C
並針對的每個後續基類 C
重複,直到找到相符項目為止:
- 如果
S
包含符合I
和M
的明確介面成員實作宣告,則這個成員是的實作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; }
end 範例
為了進行介面對應,類別或結構成員 A
會在 B
:
-
A
和B
是方法,且 和的名稱、類型和參數清單A
B
相同。 -
A
和B
是屬性、和 的名稱和類型A
B
相同,而且A
具有與 相同的存取子B
(A
如果不是明確的介面成員實作,則允許有其他存取子)。 -
A
和B
是事件,且 和 的名稱和類型A
B
相同。 -
A
和B
是索引器、和的類型A
B
和參數清單相同,而且A
具有與 相同的存取子B
(A
如果不是明確的介面成員實作,則允許有其他存取子)。
介面對應演算法的顯著影響如下:
- 明確介面成員實作在判斷實作介面成員的類別或結構成員時,優先於相同類別或結構中的其他成員。
- 非公用成員和靜態成員都不會參與介面對應。
範例:在下列程式代碼中
interface ICloneable { object Clone(); } class C : ICloneable { object ICloneable.Clone() {...} public object Clone() {...} }
ICloneable.Clone
的成員C
會Clone
成為 『ICloneable』 中的 實作,因為明確的介面成員實作優先於其他成員。end 範例
如果類別或結構實作兩個或多個介面,其中包含具有相同名稱、類型和參數類型的成員,則可以將每個介面成員對應至單一類別或結構成員。
範例:
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }
在這裡,
Paint
和IControl
的方法IForm
會對應至Paint
中的Page
方法。 當然,也可以針對這兩種方法有個別的明確介面成員實作。end 範例
如果類別或結構實作包含隱藏成員的介面,則某些成員可能需要透過明確的介面成員實作來實作。
範例:
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() {...} }
end 範例
當類別實作多個具有相同基底介面的介面時,基底介面只能有一個實作。
範例:在下列程式代碼中
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
。 事實上,這些介面沒有個別身分識別的概念。 相反地,和的實作會共用 相同的 實ITextBox
作,而且IListBox
只是考慮實作三個介面、IControl
ComboBox
和IControl
。ITextBox
IListBox
end 範例
基類的成員會參與介面對應。
範例:在下列程式代碼中
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }
中的方法
F
會用於Class1
的實作Class2's
。Interface1
end 範例
18.6.6 介面實作繼承
類別會繼承其基類所提供的所有介面實作。
若未明確重新實作介面,衍生類別就無法以任何方式改變其繼承自基類的介面對應。
範例:在宣告中
interface IControl { void Paint(); } class Control : IControl { public void Paint() {...} } class TextBox : Control { public new void Paint() {...} }
Paint
中的TextBox
方法會隱藏Paint
中的Control
方法,但不會將的Control.Paint
對應變更為IControl.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();
end 範例
不過,當介面方法對應至類別中的虛擬方法時,衍生類別可以覆寫虛擬方法並改變介面的實作。
範例:將上述宣告重寫為
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();
end 範例
由於無法將明確介面成員實作宣告為虛擬,因此無法覆寫明確的介面成員實作。 不過,明確介面成員實作呼叫另一個方法是完全有效的,而且其他方法可以宣告為虛擬,以允許衍生類別覆寫它。
範例:
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
實作。end 範例
18.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.Paint
至MyControl.Paint
。end 範例
繼承的公用成員宣告和繼承的明確介面成員宣告會參與重新實作介面的介面對應程式。
範例:
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() {} }
在這裡,的 實
IMethods
作會將介面方法對應至Derived
、Derived.F
Base.IMethods.G
、 和 。Derived.IMethods.H
Base.I
end 範例
當類別實作介面時,它也會隱含地實作該介面的所有基底介面。 同樣地,介面的重新實作也會隱含地重新實作所有介面的基底介面。
範例:
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.F
至D.F
。end 範例
18.6.8 抽象類和介面
如同非抽象類,抽象類應該提供類別基類清單中所列介面之所有成員的實作。 不過,允許抽象類將介面方法對應至抽象方法。
範例:
interface IMethods { void F(); void G(); } abstract class C : IMethods { public abstract void F(); public abstract void G(); }
在這裡,對應
IMethods
和F
抽象方法的G
實作,這應該在衍生自C
的非抽象類別中覆寫。end 範例
明確介面成員實作不可以是抽象的,但明確介面成員實作當然可以呼叫抽象方法。
範例:
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
的非抽象類別需要覆寫FF
和GG
,因此提供的實際實作IMethods
。end 範例