第五節、實現介面
1、顯式實現介面成員
為了實現介面,類可以定義顯式介面成員執行體(Explicit interface member implementations)。顯式介面成員執行體可以是一個方法、一個屬性、一個事件或者是一個索引指標的定義,定義與該成員對應的全權名應保持一致。
using System ;
interface ICloneable {
object Clone( ) ;
}
interface IComparable {
int CompareTo(object other) ;
}
class ListEntry: ICloneable, IComparable {
object ICloneable.Clone( ) {…}
int IComparable.CompareTo(object other) {…}
}
上面的代碼中ICloneable.Clone 和IComparable.CompareTo 就是顯式介面成員執行體。
說明:
1、不能在方法調用、屬性訪問以及索引指標訪問中通過全權名訪問顯式介面成員執行體。事實上,顯式介面成員執行體只能通過介面的執行個體,僅僅引用介面的成員名稱來訪問。
2、顯式介面成員執行體不能使用任何訪問限制符,也不能加上abstract, virtual, override或static 修飾符。
3、顯式介面成員執行體和其他成員有著不同的訪問方式。因為不能在方法調用、屬性訪問以及索引指標訪問中通過全權名訪問,顯式介面成員執行體在某種意義上是私人的。但它們又可以通過介面的執行個體訪問,也具有一定的公有性質。
4、只有類在定義時,把介面名寫在了基類列表中,而且類中定義的全權名、類型和傳回型別都與顯式介面成員執行體完全一致時,顯式介面成員執行體才是有效,例如:
class Shape: ICloneable {
object ICloneable.Clone( ) {…}
int IComparable.CompareTo(object other) {…}
}
使用顯式介面成員執行體通常有兩個目的:
1、因為顯式介面成員執行體不能通過類的執行個體進行訪問,這就可以從公有介面中把介面的實現部分單獨分離開。如果一個類只在內部使用該介面,而類的使用者不會直接使用到該介面,這種顯式介面成員執行體就可以起到作用。
2、顯式介面成員執行體避免了介面成員之間因為同名而發生混淆。如果一個類希望對名稱和傳回型別相同的介面成員採用不同的實現方式,這就必須要使用到顯式介面成員執行體。如果沒有顯式介面成員執行體,那麼對於名稱和傳回型別不同的介面成員,類也無法進行實現。
下面的定義是無效的,因為Shape 定義時基類列表中沒有出現介面IComparable。
class Shape: ICloneable
{
object ICloneable.Clone( ) {…}
}
class Ellipse: Shape
{
object ICloneable.Clone( ) {…}
}
在Ellipse 中定義ICloneable.Clone是錯誤的,因為Ellipse即使隱式地實現了介面ICloneable,ICloneable仍然沒有顯式地出現在Ellipse定義的基類列表中。
介面成員的全權名必須對應在介面中定義的成員。如下面的例子中,Paint的顯式介面成員執行體必須寫成IControl.Paint。
using System ;
interface IControl
{
void Paint( ) ;
}
interface ITextBox: IControl
{
void SetText(string text) ;
}
class TextBox: ITextBox
{
void IControl.Paint( ) {…}
void ITextBox.SetText(string text) {…}
}
實現介面的類可以顯式實現該介面的成員。當顯式實現某成員時,不能通過類執行個體訪問該成員,而只能通過該介面的執行個體訪問該成員。明確介面實作還允許程式員繼承共用相同成員名的兩個介面,並為每個介面成員提供一個單獨的實現。
下面例子中同時以公制單位和英制單位顯示框的尺寸。Box類繼承 IEnglishDimensions和 IMetricDimensions兩個介面,它們表示不同的度量衡系統。兩個介面有相同的成員名 Length 和 Width。
程式清單1 DemonInterface.cs
interface IEnglishDimensions {
float Length ( ) ;
float Width ( ) ;
}
interface IMetricDimensions {
float Length ( ) ;
float Width ( ) ;
}
class Box : IEnglishDimensions, IMetricDimensions {
float lengthInches ;
float widthInches ;
public Box(float length, float width) {
lengthInches = length ;
widthInches = width ;
}
float IEnglishDimensions.Length( ) {
return lengthInches ;
}
float IEnglishDimensions.Width( ) {
return widthInches ;
}
float IMetricDimensions.Length( ) {
return lengthInches * 2.54f ;
}
float IMetricDimensions.Width( ) {
return widthInches * 2.54f ;
}
public static void Main( ) {
//定義一個實類對象 "myBox"::
Box myBox = new Box(30.0f, 20.0f);
// 定義一個介面" eDimensions"::
IEnglishDimensions eDimensions = (IEnglishDimensions) myBox;
IMetricDimensions mDimensions = (IMetricDimensions) myBox;
// 輸出:
System.Console.WriteLine(" Length(in): {0}", eDimensions.Length( ));
System.Console.WriteLine(" Width (in): {0}", eDimensions.Width( ));
System.Console.WriteLine(" Length(cm): {0}", mDimensions.Length( ));
System.Console.WriteLine(" Width (cm): {0}", mDimensions.Width( ));
}
}
輸出:Length(in): 30,Width (in): 20,Length(cm): 76.2,Width (cm): 50.8
代碼討論:如果希望預設度量採用英制單位,請正常實現 Length 和 Width 這兩個方法,並從 IMetricDimensions 介面顯式實現 Length 和 Width 方法:
public float Length( ) {
return lengthInches ;
}
public float Width( ){
return widthInches;
}
float IMetricDimensions.Length( ) {
return lengthInches * 2.54f ;
}
float IMetricDimensions.Width( ) {
return widthInches * 2.54f ;
}
這種情況下,可以從類執行個體訪問英制單位,而從介面執行個體訪問公制單位:
System.Console.WriteLine("Length(in): {0}", myBox.Length( )) ;
System.Console.WriteLine("Width (in): {0}", myBox.Width( )) ;
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length( )) ;
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width( )) ;
2、繼承介面實現
介面具有不變性,但這並不意味著介面不再發展。類似於類的繼承性,介面也可以繼承和發展。
注意:介面繼承和類繼承不同,首先,類繼承不僅是說明繼承,而且也是實現繼承;而介面繼承只是說明繼承。也就是說,衍生類別可以繼承基類的方法實現,而派生的介面只繼承了父介面的成員方法說明,而沒有繼承父介面的實現,其次,C#中類繼承只允許單繼承,但是介面繼承允許多繼承,一個子介面可以有多個父介面。
介面可以從零或多個介面中繼承。從多個介面中繼承時,用":"後跟被繼承的介面名字,多個介面名之間用","分割。被繼承的介面應該是可以訪問得到的,比如從private 類型或internal 類型的介面中繼承就是不允許的。介面不允許直接或間接地從自身繼承。和類的繼承相似,介面的繼承也形成介面之間的階層。
請看下面的例子:
using System ;
interface IControl {
void Paint( ) ;
}
interface ITextBox: IControl {
void SetText(string text) ;
}
interface IListBox: IControl {
void SetItems(string[] items) ;
}
interface IComboBox: ITextBox, IListBox { }
對一個介面的繼承也就繼承了介面的所有成員,上面的例子中介面ITextBox和IListBox都從介面IControl中繼承,也就繼承了介面IControl的Paint方法。介面IComboBox從介面ITextBox和IListBox中繼承,因此它應該繼承了介面ITextBox的SetText方法和IListBox的SetItems方法,還有IControl的Paint方法。
一個類繼承了所有被它的基本類提供的介面實現程式。
不通過顯式的實現一個介面,一個衍生類別不能用任何方法改變它從它的基本類繼承的介面映射。例如,在聲明中
interface IControl {
void Paint( );
}
class Control: IControl {
public void Paint( ) {...}
}
class TextBox: Control {
new public void Paint( ) {...}
}
TextBox 中的方法Paint 隱藏了Control中的方法Paint ,但是沒有改變從Control.Paint 到IControl.Paint 的映射,而通過類執行個體和介面執行個體調用Paint將會有下面的影響
Control c = new Control( ) ;
TextBox t = new TextBox( ) ;
IControl ic = c ;
IControl it = t ;
c.Paint( ) ; // 影響Control.Paint( ) ;
t.Paint( ) ; // 影響TextBox.Paint( ) ;
ic.Paint( ) ; // 影響Control.Paint( ) ;
it.Paint( ) ; // 影響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( ) ; // 影響Control.Paint( );
t.Paint( ) ; // 影響TextBox.Paint( );
ic.Paint( ) ; // 影響Control.Paint( );
it.Paint( ) ; // 影響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 繼承的類可以通過覆蓋方法PaintControl 來對IControl.Paint 的實現程式進行特殊化。
3、重新實現介面
我們已經介紹過,衍生類別可以對基類中已經定義的成員方法進行重載。類似的概念引入到類對介面的實現中來,叫做介面的重實現(re-implementation)。繼承了介面實現的類可以對介面進行重實現。這個介面要求是在類定義的基類列表中出現過的。對介面的重實現也必須嚴格地遵守首次實現介面的規則,派生的介面映射不會對為介面的重實現所建立的介面映射產生任何影響。
下面的代碼給出了介面重實現的例子:
interface IControl {
void Paint( ) ;
class Control: IControl
void IControl.Paint( ) {…}
class MyControl: Control, IControl
public void Paint( ) {}
}
實際上就是:Control把IControl.Paint映射到了Control.IControl.Paint上,但這並不影響在MyControl中的重實現。在MyControl中的重實現中,IControl.Paint被映射到MyControl.Paint 之上。
在介面的重實現時,繼承而來的公有成員定義和繼承而來的顯式介面成員的定義參與到介面映射的過程。
using System ;
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。前面我們說過,類在實現一個介面時,同時隱式地實現了該介面的所有父介面。同樣,類在重實現一個介面時同時,隱式地重實現了該介面的所有父介面。
using System ;
interface IBase {
void F( ) ;
}
interface IDerived: IBase {
void G( ) ;
}
class C: IDerived {
void IBase.F( ) {
//對F 進行實現的代碼…
}
void IDerived.G( ) {
//對G 進行實現的代碼…
}
}
class D: C, IDerived {
public void F( ) {
//對F 進行實現的代碼…
}
public void G( ) {
//對G 進行實現的代碼…
}
}
這裡,對IDerived的重實現也同樣實現了對IBase的重實現,把IBase.F 映射到了D.F。
4、映射介面
類必須為在基類表中列出的所有介面的成員提供具體的實現。在類中定位介面成員的實現稱之為介面映射(interface mapping )。
映射,數學上表示一一對應的函數關係。介面映射的含義也是一樣,介面通過類來實現,那麼對於在介面中定義的每一個成員,都應該對應著類的一個成員來為它提供具體的實現。
類的成員及其所映射的介面成員之間必須滿足下列條件:
1、如果A和B都是成員方法,那麼A和B的名稱、類型、形參表(包括參數個數和每一個參數的類型)都應該是一致的。
2、如果A和B都是屬性,那麼A和B的名稱、類型應當一致,而且A和B的訪問器也是類似的。但如果A不是顯式介面成員執行體,A允許增加自己的訪問器。
3、如果A和B都是時間那麼A和B的名稱、類型應當一致。
4、如果A和B都是索引指標,那麼A和B的類型、形參表(包括參數個數和每一個參數的類型)應當一致。而且A和B的訪問器也是類似的。但如果A不是顯式介面成員執行體,A允許增加自己的訪問器。
那麼,對於一個介面成員,怎樣確定由哪一個類的成員來實現呢?即一個介面成員映射的是哪一個類的成員?在這裡,我們敘述一下介面映射的過程。假設類C實現了一個介面IInterface,Member是介面IInterface中的一個成員,在定位由誰來實現介面成員Member,即Member的映射過程是這樣的:
1、如果C中存在著一個顯式介面成員執行體,該執行體與介面IInterface 及其成員Member相對應,則由它來實現Member 成員。
2、如果條件(1)不滿足,且C中存在著一個非靜態公有成員,該成員與介面成員Member相對應,則由它來實現Member 成員。
3、如果上述條件仍不滿足,則在類C定義的基類列表中尋找一個C 的基類D,用D來代替C。
4、重複步驟1-- 3 ,遍曆C的所有直接基類和非直接基類,直到找到一個滿足條件的類的成員。
5、如果仍然沒有找到,則報告錯誤。
下面是一個調用基類方法來實現介面成員的例子。類Class2 實現了介面Interface1,類Class2 的基類Class1 的成員也參與了介面的映射,也就是說類Class2 在對介面Interface1進行實現時,使用了類Class1提供的成員方法F來實現介面Interface1的成員方法F:
interface Interface1 {
void F( ) ;
}
class Class1 {
public void F( ) { }
public void G( ) { }
}
class Class2: Class1, Interface1 {
new public void G( ) {}
}
注意:介面的成員包括它自己定義的成員,而且包括該介面所有父介面定義的成員。在介面映射時,不僅要對介面定義體中顯式定義的所有成員進行映射,而且要對隱式地從父介面那裡繼承來的所有介面成員進行映射。
在進行介面映射時,還要注意下面兩點:
1、在決定由類中的哪個成員來實現介面成員時,類中顯式說明的介面成員比其它成員優先實現。
2、使用Private、protected和static修飾符的成員不能參與實現介面映射。例如:
interface ICloneable {
object Clone( ) ;
}
class C: ICloneable {
object ICloneable.Clone( ) {…}
public object Clone( ) {…}
}
例子中成員ICloneable.Clone 稱為介面ICloneable 的成員Clone 的實現者,因為它是顯式說明的介面成員,比其它成員有著更高的優先權。
如果一個類實現了兩個或兩個以上名字、類型和參數類型都相同的介面,那麼類中的一個成員就可能實現所有這些介面成員:
interface IControl {
void Paint( ) ;
}
interface IForm {
void Paint( ) ;
}
class Page: IControl, IForm {
public void Paint( ) {…}
}
這裡,介面IControl和IForm的方法Paint都映射到了類Page中的Paint方法。當然也可以分別用顯式的介面成員分別實現這兩個方法:
interface IControl {
void Paint( ) ;
}
interface IForm {
void Paint( ) ;
}
class Page: IControl, IForm {
public void IControl.Paint( ) {
//具體的介面實現代碼
}
public void IForm.Paint( ) {
//具體的介面實現代碼
}
}
上面的兩種寫法都是正確的。但是如果介面成員在繼承中覆蓋了父介面的成員,那麼對該介面成員的實現就可能必須映射到顯式介面成員執行體。看下面的例子:
interface IBase {
int P { get; }
}
interface IDerived: IBase {
new int P( ) ;
}
介面IDerived從介面IBase中繼承,這時介面IDerived 的成員方法覆蓋了父介面的成員方法。因為這時存在著同名的兩個介面成員,那麼對這兩個介面成員的實現如果不採用顯式介面成員執行體,編譯器將無法分辨介面映射。所以,如果某個類要實現介面IDerived,在類中必須至少定義一個顯式介面成員執行體。採用下面這些寫法都是合理的:
//一:對兩個介面成員都採用顯式介面成員執行體來實現
lass C: IDerived {
int IBase.P
get
{ //具體的介面實現代碼 }
int IDerived.P( ){
//具體的介面實現代碼 }
}
//二:對Ibase 的介面成員採用顯式介面成員執行體來實現
class C: IDerived {
int IBase.P
get {//具體的介面實現代碼}
public int P( ){
//具體的介面實現代碼 }
}
//三:對IDerived 的介面成員採用顯式介面成員執行體來實現
class C: IDerived{
public int P
get {//具體的介面實現代碼}
int IDerived.P( ){
//具體的介面實現代碼}
}
另一種情況是,如果一個類實現了多個介面,這些介面又擁有同一個父介面,這個父介面只允許被實現一次。
using System ;
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) {…}
}
上面的例子中,類ComboBox實現了三個介面:IControl,ITextBox和IListBox。如果認為ComboBox不僅實現了IControl介面,而且在實現ITextBox和IListBox的同時,又分別實現了它們的父介面IControl。實際上,對介面ITextBox 和IListBox 的實現,分享了對介面IControl 的實現。
我們對C#的介面有了較全面的認識,基本掌握了怎樣應用C#的介面編程,但事實上,C#的不僅僅應用於.NET平台,它同樣支援以前的COM,可以實現COM類到.NET類的轉換,如C#調用API。