對接口成員的通路
對接口方法的調用和采用索引訓示器通路的規則與類中的情況也是相同的。如果底層成員的命名與繼承而來的高層成員一緻,那麼底層成員将覆寫同名的高層成員。但由于接口支援多繼承,在多繼承中,如果兩個父接口含有同名的成員,這就産生了二義性(這也正是C#中取消了類的多繼承機制的原因之一),這時需要進行顯式的定義:
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( )是正确的調用方式。
注釋:接口本身不提供所定義的成員的實作,它僅僅說明這些成員,這些成員必須依靠實作接口的類或其它接口的支援。
知道了怎樣通路接口,我們還要知道怎樣實作接口,要實作C#的接口,請看下一節-實作接口
類對接口的實作
前面我們已經說過,接口定義不包括方法的實作部分。接口可以通過類或結構來實作。我們主要講述通過類來實作接口。用類來實作接口時,接口的名稱必須包含在類定義中的基類清單中。
下面的例子給出了由類來實作接口的例子。其中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( )是正确的調用方式。
注釋:接口本身不提供所定義的成員的實作,它僅僅說明這些成員,這些成員必須依靠實作接口的類或其它接口的支援。
知道了怎樣通路接口,我們還要知道怎樣實作接口,要實作C#的接口,請看下一節-實作接口