第五节、实现接口
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方法。
一个类继承了所有被它的基本类提供的接口实现程序。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)不通过显式的实现一个接口,一个派生类不能用任何方法改变它从它的基本类继承的接口映射。例如,在声明中
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)。继承了接口实现的类可以对接口进行重实现。这个接口要求是在类定义的基类列表中出现过的。对接口的重实现也必须严格地遵守首次实现接口的规则,派生的接口映射不会对为接口的重实现所建立的接口映射产生任何影响。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)下面的代码给出了接口重实现的例子:
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 成员。