全面剖析C#接口编程之实现接口 (1)
接口(interface)用来定义一种程序的协定。实现接口的类或者结构要与接口的定义严格一致。在前面的文章中,我们已经对C#接口的概念,如何定义接口以及如何对接口进行访问等问题进行了详细的讨论。在这些知识的基础上,本文我们将来了解实现接口的方法。
为了实现接口,类可以定义显式接口成员执行体(Explicit interface member
implementations)。显式接口成员执行体可以是一个方法、一个属性、一个事件或者是一个索引指示器的定义,定义与该成员对应的全权名应保持一致。
using System ; |
上面的代码中ICloneable.Clone 和IComparable.CompareTo 就是显式接口成员执行体。
说明:
· 不能在方法调用、属性访问以及索引指示器访问中通过全权名访问显式接口成员执行体。事实上,显式接口成员执行体只能通过接口的实例,仅仅引用接口的成员名称来访问。
· 显式接口成员执行体不能使用任何访问限制符,也不能加上abstract, virtual, override或static 修饰符。
· 显式接口成员执行体和其他成员有着不同的访问方式。因为不能在方法调用、属性访问以及索引指示器访问中通过全权名访问,显式接口成员执行体在某种意义上是私有的。但它们又可以通过接口的实例访问,也具有一定的公有性质。
· 只有类在定义时,把接口名写在了基类列表中,而且类中定义的全权名、类型和返回类型都与显式接口成员执行体完全一致时,显式接口成员执行体才是有效的,例如:
class Shape: ICloneable { |
使用显式接口成员执行体通常有两个目的:
· 因为显式接口成员执行体不能通过类的实例进行访问,这就可以从公有接口中把接口的实现部分单独分离开。如果一个类只在内部使用该接口,而类的使用者不会直接使用到该接口,这种显式接口成员执行体就可以起到作用。
· 显式接口成员执行体避免了接口成员之间因为同名而发生混淆。如果一个类希望对名称和返回类型相同的接口成员采用不同的实现方式,这就必须要使用到显式接口成员执行体。如果没有显式接口成员执行体,那么对于名称和返回类型不同的接口成员,类也无法进行实现。
下面的定义是无效的,因为Shape 定义时基类列表中没有出现接口IComparable。
class Shape: ICloneable |
在Ellipse中定义ICloneable.Clone是错误的,因为Ellipse即使隐式地实现了接口ICloneable,ICloneable仍然没有显式地出现在Ellipse定义的基类列表中。
接口成员的全权名必须对应在接口中定义的成员。如下面的例子中,Paint的显式接口成员执行体必须写成IControl.Paint。
using System ; |
实现接口的类可以显式实现该接口的成员。当显式实现某成员时,不能通过类实例访问该成员,而只能通过该接口的实例访问该成员。显式接口实现还允许程序员继承共享相同成员名的两个接口,并为每个接口成员提供一个单独的实现。
下面例子中同时以公制单位和英制单位显示框的尺寸。Box类继承IEnglishDimensions和IMetricDimensions两个接口,它们表示不同的度量衡系统。两个接口有相同的成员名 Length 和 Width。
程序清单1 DemonInterface.cs |
输出:Length(in): 30,Width (in): 20,Length(cm): 76.2,Width (cm): 50.8
代码讨论:如果希望默认度量采用英制单位,请正常实现 Length 和 Width 这两个方法,并从 IMetricDimensions 接口显式实现 Length 和 Width 方法:
public float Length( ) { |
这种情况下,可以从类实例访问英制单位,而从接口实例访问公制单位:
System.Console.WriteLine("Length(in): {0}", myBox.Length( )) ; |
接口具有不变性,但这并不意味着接口不再发展。类似于类的继承性,接口也可以继承和发展。
注意:接口继承和类继承不同,首先,类继承不仅是说明继承,而且也是实现继承;而接 口继承只是说明继承。也就是说,派生类可以继承基类的方法实现,而派生的接口只继承了父接口的成员方法说明,而没有继承父接口的实现,其次,C#中类继承 只允许单继承,但是接口继承允许多继承,一个子接口可以有多个父接口。
接口可以从零或多个接口中继承。从多个接口中继承时,用":"后跟被继承的接口名 字,多个接口名之间用","分割。被继承的接口应该是可以访问得到的,比如从private类型或internal类型的接口中继承就是不允许的。接口不 允许直接或间接地从自身继承。和类的继承相似,接口的继承也形成接口之间的层次结构。
请看下面的例子:
using System ; |
对一个接口的继承也就继承了接口的所有成员,上面的例子中接口ITextBox和 IListBox都从接口IControl中继承,也就继承了接口IControl的Paint方法。接口IComboBox从接口ITextBox和 IListBox中继承,因此它应该继承了接口ITextBox的SetText方法和IListBox的SetItems方法,还有IControl的 Paint方法。
一个类继承了所有被它的基本类提供的接口实现程序。
不通过显式的实现一个接口,一个派生类不能用任何方法改变它从它的基本类继承的接口映射。例如,在声明中:
interface IControl { |
TextBox中的方法Paint隐藏了Control中的方法Paint,但是没有改变从Control.Paint到IControl.Paint 的映射,而通过类实例和接口实例调用Paint将会有下面的影响。
Control c = new Control( ) ; |
但是,当一个接口方法被映射到一个类中的虚拟方法,派生类就不可能覆盖这个虚拟方法并且改变接口的实现函数。例如,把上面的声明重新写为:
interface IControl { |
就会看到下面的结果:
Control c = new Control( ) ; |
由于显式接口成员实现程序不能被声明为虚拟的,就不可能覆盖一个显式接口成员实现程序。一个显式接口成员实现程序调用另外一个方法是有效的,而另外的那个方法可以被声明为虚拟的以便让派生类可以覆盖它。例如:
interface IControl { |
这里,从Control继承的类可以通过覆盖方法PaintControl来对IControl.Paint的实现程序进行特殊化。