15.4.1 類對接口的實現
前面我們已經說過,接口定義不包括方法的實現部分。接口可以通過類或結構來實現。我們主要講述通過類來實現接口。用類來實現接口時,接口的名稱必須包括在類聲明中的基類列表中。
下面的例子給出了由類來實現接口的例子。其中ISequence為一個隊列接口,提供了向隊列尾部添加對象的成員方法Add(),IRing為一個循環表接口,提供了向環中插入對象的方法Insert(object obj),方法返回插入的位置。類RingSquence實現了接口ISequence和接口IRing。
程序清單15-4:
using System;
interface ISequence
{
object Add();
}
interface IRing
{
int Insert(object obj);
}
class RingSequence:ISequence,IRing
{
public object Add(){...}
public int Insert(object obj){...}
}
如果類實現了某個接口,類也隱式地繼承了該接口的所有父接口,不管這些父接口有沒有在類聲明的基類列表中列出。
using System;
interface IControl
{
void Paint();
}
interface ITextBox:IControl
{
void SetText(string text);
}
class TextBox:ITextBox
{
public void Paint(){...}
public void SetText(string text){...}
}
這裡,類TextBox不僅實現了接口ITextBox,還實現了接口ITextBox的父接口IControl。
前面我們已經看到,一個類可以實現多個接口。再看下面的例子:
using System;
interface IControl
{
void Paint();
}
interface IDataBound
{
void Bind(Binder b);
}
public class Control:IControl
{
public void Paint(){...}
}
public class EditBox:Control,IControl,IDataBound
{
public void Paint(){...}
public void Bind(Binter b){...}
}
上例中,類EditBox從Control類繼承並同時實現了IControl and IDataBound接口。EditBox中的Paint方法來自IControl接口,Bind方法來自IDataBound接口,二者在EditBox類中都作為公有成員實現。當然,在C#中我們也可以選擇不作為公有成員實現接口。
如果每個成員都明顯地指出了被實現的接口,通過這種途徑被實現的接口我們稱之為顯式接口成員(explicit interface member)。用這種方式我們改寫上面的例子:
public class EditBox:IControl,IDataBound
{
void IControl.Paint(){...}
void IDataBound.Bind(Binder b){...}
}
顯式接口成員只能通過接口調用。例如:
class Test
{
static void Main(){
EditBox editbox=new EditBox();
editbox.Paint(); //error:no such method
IControl control=editbox;
control.Paint(); //calls EditBox's Paint implementation
}
}
上述代碼中對editbox.Paint()的調用是錯誤的,因為editbox本身並沒有提供這一方法。control.Paint()是正確的調用方式。
注意:接口本身不提供所定義的成員的實現,它僅僅說明這些成員,這些成員必須依靠實現接口的類或其它接口的支持。
15.4.2 顯式接口成員執行體
為了實現接口,類可以聲明顯式接口成員執行體(Explicit interface member implimentations)。顯式接口成員執行體可以是一個方法、一個屬性、一個事件或者是一個索引指示器的聲明,聲明與該成員對應的全權名應保持一致。
using System;
interface ICloneable
{
object Clone();
}
interface IComparable
{
int Compare To(object other);
}
class ListEntry:ICloneable,IComparable
{
object ICloneable.Clone(){...}
int IComparable.CompareTo(object other){...}
}
上面的代碼中ICloneable.Clone和IComparable.CompareTo就是顯式接口成員執行體。
不能在方法調用、屬性訪問以及索引指示器訪問中通過全權名訪問顯式接口成員執行體。事實上,顯式接口成員執行體只能通過接口的實例,僅僅引用接口的成員名稱來訪問。
顯式接口成員執行體不能使用任何訪問限制符,也不能加上abstract,virtual,override或static修飾符。
顯式接口成員執行體和其他成員有著不同的訪問方式。因為不能在方法調用、屬性訪問以及索引指示器訪問中通過全權名訪問,顯式接口成員執行體在某種意義上是私有的。但它們又可以通過接口的實例訪問,也具有一定的公有性質。使用顯式接口成員執行體通常有兩個目的:
●因為顯式接口成員執行體不能通過類的實例進行訪問,這就可以從公有接口中把接口的實現部分單獨分離開。如果一個類只在內部使用該接口,而類的使用者不會直接使用該接口,這種顯式接口成員執行體就可以起到作用。
●顯式接口成員執行體避免了接口成員之間因為同名而發生混淆。如果一個類希望對名稱和返回類型相同的接口成員采用不同的實現方式,這就必須要使用到顯式接口成員執行體。如果沒有顯式接口成員執行體,那麼對於名稱和返回類型不同的接口成員,類也無法進行實現。
只有類在聲明時,把接口名寫在了基類列表中,而且類中聲明的全權名、類型和返回類型都與顯式接口成員執行體完全一致時,顯式接口成員執行體才是有效的。例如:
class Shape:ICloneable
{
object ICloneable.Clone(){...}
int IComparable.CompareTo(object other){...}
}
這是一個無效的聲明,因為Shape聲明時基類列表中沒有出現接口IComparable。下面的代碼同樣也有錯誤:
class Shape:ICloneable
{
object ICloneable.Clone(){...}
}
class Ellips:Shape
{
object ICloneable.Clone(){...}
}
在Ellips中聲明ICloneable.Clone是錯誤的,因為Ellips即使隱式地實現了接口ICloneable,ICloneable仍然沒有顯式地出現在Ellips聲明的基類列表中。
接口成員的全權名必須對應在接口中聲明的成員。如下面例子中,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){...}
}
15.4.3 接口映射
類必須為在基類表中列出的所有接口的成員提供具體的實現。在類中定位接口成員的實現稱之為接口映射(interface mapping)。
映射,數學上表示一一對應的函數關系。接口映射的含義也是一樣,接口通過類來實現,那麼對於在接口中聲明的每一個成員,都應該對應著類的一個成員來為它提供具體的實現。
類的成員及其所映射的接口成員之間必須滿足下列條件:
●如果A和B都是成員方法,那麼A和B的名稱、類型、形參表(包括參數個數和每一個參數的類型)都應該是一致的。
●如果A和B都是屬性,那麼A和B的名稱、類型應當一致,而且A和B的訪問器也是類似的。但如果A不是顯式接口成員執行體,A允許增加自己的訪問器。
●如果A和B都是時間,那麼A和B的名稱、類型應當一致。
●如果A和B都是索引指示器,那麼A和B的類型、形參表(包括參數個數和每一個參數的類型)應當一致。而且A和B的訪問器也是類似的。但如果A不是顯式接口成員執行體,A允許增加自己的訪問器。
那麼,對於一個接口成員,怎樣確定由哪一個類的成員來實現呢?即一個接口成員映射的是哪一個類的成員?在這裡我們敘述一下接口映射的過程。假設類C實現了一個接口IInterface,Member是接口IIterface中的一個成員。在定位由誰來實現接口成員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(){}
}
注意:接口的成員包括它自己聲明的成員,而且包括該接口所有父接口聲明的成員。在接口映射時,不僅要對接口聲明體中顯式聲明的所有成員進行映射,而且要對隱式地從父接口那裡繼承來的所有接口成員進行映射。
在進行接口映射時,還要注意下面兩點:
●在決定由類中的哪個成員來實現接口成員時,類中顯式說明的接口成員比其它成員優先實現。
●使用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,在類中必須至少聲明一個顯式接口成員執行體。采用下面這些寫法都是合理的:
//一、對兩個接口成員都采用顯式接口成員執行體來實現。
class 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的實現。
15.4.4 接口實現的繼承機制
一個類繼承了它的基類提供的所有接口的實現
如果不顯式地重新實現接口,派生類就無法改變從基類中繼承來的接口映射。
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(); //invokes Control.Paint(); t.Paint(); //invokes TextBox.Paint(); ic.Paint(); //invokes Control.Paint(); it.Paint(); //invokes Control.Paint();
但是,當一個interface方法被映射到類中的一個虛方法時,派生類就可以重載這個虛方法,並且改變這個接口的實現。我們改寫一下上例中的代碼:
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。
15.4.5 接口的重實現
我們已經介紹過,派生類可以對基類中已經定義的成員方法進行重載。類似的概念引入到類對接口的實現中來,叫做接口的重實現(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 IMthods.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。