第四节访问接口
对接口成员的访问
对接口方法的调用和采用索引指示器访问的规则与类中的情况也是相同的如果底层成员的命名与继承而来的高层成员一致那么底层成员将覆盖同名的高层成员但由于接口支持多继承在多继承中如果两个父接口含有同名的成员这就产生了二义性(这也正是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)
{
//rsCount() ; 错误 Count 有二义性
//rsCount = ; 错误 Count 有二义性
((ISequence)rs)Count = ; // 正确
((IRing)rs)Count() ; // 正确调用IRingCount
}
}
上面的例子中前两条语句rs Count()和rs Count = 会产生二义性从而导致编译时错误因此必须显式地给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)
{
// NumAdd() ; 错误
NumAdd() ; // 正确
((IInteger)n)Add() ; // 正确
((IDouble)n)Add() ; // 正确
}
}
调用NumAdd() 会导致二义性因为候选的重载方法的参数类型均适用但是调用NumAdd() 是允许的因为 是浮点数参数类型与方法IIntegerAdd()的参数类型不一致这时只有IDoubleAdd 才是适用的不过只要加入了显式的指派就决不会产生二义性
接口的多重继承的问题也会带来成员访问上的问题例如
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 () ; // 调用ILeft FWay
((IBase)d) FWay () ; // 调用IBase FWay
((ILeft)d) FWay () ; // 调用ILeft FWay
((IRight)d) FWay () ; // 调用IBase FWay
}
}
上例中方法IBaseFWay在派生的接口ILeft中被Ileft的成员方法FWay覆盖了所以对d FWay ()的调用实际上调用了虽然从IBase> IRight> IDerived这条继承路径上来看ILeftFWay方法是没有被覆盖的我们只要记住这一点一旦成员被覆盖以后所有对其的访问都被覆盖以后的成员拦截了
类对接口的实现
前面我们已经说过接口定义不包括方法的实现部分接口可以通过类或结构来实现我们主要讲述通过类来实现接口用类来实现接口时接口的名称必须包含在类定义中的基类列表中
下面的例子给出了由类来实现接口的例子其中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可以改作方法IcontrolPaint和IdataBoundBind来来实现
public class EditBox: IControl IDataBound
{
void IControlPaint( ) {}
void IDataBoundBind(Binder b) {}
}
因为通过外部指派接口成员实现了每个成员所以用这种方法实现的成员称为外部接口成员外部接口成员可以只是通过接口来调用例如Paint方法中EditBox的实现可以只是通过创建Icontrol接口来调用
class Test
{
static void Main( )
{
EditBox editbox = new EditBox( );
editboxPaint( ); //错误: EditBox 没有Paint 事件
IControl control = editbox;
controlPaint( ); // 调用 EditBox的Paint事件
}
}
上例中类EditBox 从Control 类继承并同时实现了IControl and IDataBound 接口EditBox 中的Paint 方法来自IControl 接口Bind 方法来自IDataBound 接口二者在EditBox 类中都作为公有成员实现当然在C# 中我们也可以选择不作为公有成员实现接口
如果每个成员都明显地指出了被实现的接口通过这种途径被实现的接口我们称之为显式接口成员(explicit interface member) 用这种方式我们改写上面的例子
public class EditBox: IControl IDataBound
{
void IControlPaint( ) {…}
void IDataBoundBind(Binder b) {…}
}
显式接口成员只能通过接口调用例如
class CTest
{
static void Main( )
{
EditBox editbox = new EditBox( ) ;
editboxPaint( ) ; //错误:不同的方法
IControl control = editbox;
controlPaint( ) ; //调用 EditBox的Paint方法
}
}
上述代码中对editboxPaint( )的调用是错误的因为editbox 本身并没有提供这一方法controlPaint( )是正确的调用方式
注释接口本身不提供所定义的成员的实现它仅仅说明这些成员这些成员必须依靠实现接口的类或其它接口的支持
知道了怎样访问接口我们还要知道怎样实现接口要实现C#的接口请看下一节实现接口