using System ; interface ISequence { int Count { get; set; } } interface IRing { void Count(int i) ; } interface IRingSequence: ISequence, IRing { } class CTest { void Test(IRingSequence rs) { //rs.Count(1) ; 錯誤, Count 有二義性 //rs.Count = 1; 錯誤, Count 有二義性 ((ISequence)rs).Count = 1; // 正確 ((IRing)rs).Count(1) ; // 正確調用IRing.Count } } |
上面的例子中,前兩條語句rs.Count(1)和rs.Count=1會產生二義性,從而導致編譯時錯誤,因此必須顯式地給rs 指派父接口類型,這種指派在運行時不會帶來額外的開銷。
再看下面的例子:
using System ; interface IInteger { void Add(int i) ; } interface IDouble { void Add(double d) ; } interface INumber: IInteger, IDouble {} class CMyTest { void Test(INumber Num) { // Num.Add(1) ; 錯誤 Num.Add(1.0) ; // 正確 ((IInteger)n).Add(1) ; // 正確 ((IDouble)n).Add(1) ; // 正確 } } |
調用Num.Add(1) 會導致二義性,因爲候選的重載方法的參數類型均適用。但是,調用Num.Add(1.0) 是允許的,因爲1.0是浮點數參數類型與方法IInteger.Add()的參數類型不一致,這時只有IDouble.Add 纔是適用的。不過只要加入了顯式的指派,就決不會產生二義性。
接口的多重繼承的問題也會帶來成員訪問上的問題。例如:
interface IBase { void FWay(int i) ; } interface ILeft: IBase { new void FWay (int i) ; } interface IRight: IBase { void G( ) ; } interface IDerived: ILeft, IRight { } class CTest { void Test(IDerived d) { d. FWay (1) ; // 調用ILeft. FWay ((IBase)d). FWay (1) ; // 調用IBase. FWay ((ILeft)d). FWay (1) ; // 調用ILeft. FWay ((IRight)d). FWay (1) ; // 調用IBase. FWay } } |
上例中,方法IBase.FWay在派生的接口ILeft中被Ileft的成員方法FWay覆蓋了。所以對d. FWay (1)的調用實際上調用了。雖然從IBase-> IRight-> IDerived這條繼承路徑上來看,ILeft.FWay方法是沒有被覆蓋的。我們只要記住這一點:一旦成員被覆蓋以後,所有對其的訪問都被覆蓋以後的成員"攔截"了。
前面我們已經說過,接口定義不包括方法的實現部分。接口可以通過類或結構來實現。我們主要講述通過類來實現接口。用類來實現接口時,接口的名稱必須包含在類定義中的基類列表中。
下面的例子給出了由類來實現接口的例子。其中ISequence爲一個隊列接口,提供了向隊列尾部添加對象的成員方法Add( ),IRing 爲一個循環表接口,提供了向環中插入對象的方法Insert(object obj),方法返回插入的位置。類RingSquence 實現了接口ISequence 和接口IRing。
using System ; interface ISequence { object Add( ) ; } 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); } interface IListBox: IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox { } |
這裏,接口IcomboBox繼承了ItextBox和IlistBox。類TextBox不僅實現了接口ITextBox,還實現了接口ITextBox 的父接口IControl。
前面我們已經看到,一個類可以實現多個接口。再看下面的例子:
interface IDataBound { void Bind(Binder b); } public class EditBox: Control, IControl, IDataBound { public void Paint( ); public void Bind(Binder b) {...} } |
類EditBox從類Control中派生並且實現了Icontrol和IdataBound。在前面的例子中接口Icontrol中的Paint方法和IdataBound接口中的Bind方法都用類EditBox中的公共成員實現。C#提供一種實現這些方法的可選擇的途徑,這樣可以使執行這些的類避免把這些成員設定爲公共的。接口成員可以用有效的名稱來實現。例如,類EditBox可以改作方法Icontrol.Paint和IdataBound.Bind來來實現。
public class EditBox: IControl, IDataBound { void IControl.Paint( ) {...} void IDataBound.Bind(Binder b) {...} } |
因爲通過外部指派接口成員實現了每個成員,所以用這種方法實現的成員稱爲外部接口成員。外部接口成員可以只是通過接口來調用。例如,Paint方法中EditBox的實現可以只是通過創建Icontrol接口來調用。
class Test { static void Main( ) { EditBox editbox = new EditBox( ); editbox.Paint( ); //錯誤: EditBox 沒有Paint 事件 IControl control = editbox; control.Paint( ); // 調用 EditBox的Paint事件 } } |
上例中,類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 CTest { static void Main( ) { EditBox editbox = new EditBox( ) ; editbox.Paint( ) ; //錯誤:不同的方法 IControl control = editbox; control.Paint( ) ; //調用 EditBox的Paint方法 } } |
上述代碼中對editbox.Paint()的調用是錯誤的,因爲editbox本身並沒有提供這一方法。control.Paint( )是正確的調用方式。
註釋:接口本身不提供所定義的成員的實現,它僅僅說明這些成員,這些成員必須依靠實現接口的類或其它接口的支持。
知道了怎樣訪問接口,我們還要知道怎樣實現接口,這部分內容我們將在下一篇文章中詳細講述。