明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

基于C#的接口基礎圖文說明教程之5

[摘要]第五節(jié)、實現(xiàn)接口  1、顯式實現(xiàn)接口成員  為了實現(xiàn)接口,類可以定義顯式接口成員執(zhí)行體(Explicit interface member implementations)。顯式接口成員執(zhí)行體可以是一個方法、一個屬性、一個事件或者是一個索引指示器的定義,定義與該成員對應的全權(quán)名應保持一致。usin...

  第五節(jié)、實現(xiàn)接口

  1、顯式實現(xiàn)接口成員

  為了實現(xiàn)接口,類可以定義顯式接口成員執(zhí)行體(Explicit interface member implementations)。顯式接口成員執(zhí)行體可以是一個方法、一個屬性、一個事件或者是一個索引指示器的定義,定義與該成員對應的全權(quán)名應保持一致。

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 就是顯式接口成員執(zhí)行體。

  說明:

  1、不能在方法調(diào)用、屬性訪問以及索引指示器訪問中通過全權(quán)名訪問顯式接口成員執(zhí)行體。事實上,顯式接口成員執(zhí)行體只能通過接口的實例,僅僅引用接口的成員名稱來訪問。

  2、顯式接口成員執(zhí)行體不能使用任何訪問限制符,也不能加上abstract, virtual, override或static 修飾符。

  3、顯式接口成員執(zhí)行體和其他成員有著不同的訪問方式。因為不能在方法調(diào)用、屬性訪問以及索引指示器訪問中通過全權(quán)名訪問,顯式接口成員執(zhí)行體在某種意義上是私有的。但它們又可以通過接口的實例訪問,也具有一定的公有性質(zhì)。

  4、只有類在定義時,把接口名寫在了基類列表中,而且類中定義的全權(quán)名、類型和返回類型都與顯式接口成員執(zhí)行體完全一致時,顯式接口成員執(zhí)行體才是有效的,例如:

class Shape: ICloneable {
object ICloneable.Clone( ) {…}
int IComparable.CompareTo(object other) {…}
}
  使用顯式接口成員執(zhí)行體通常有兩個目的:

  1、因為顯式接口成員執(zhí)行體不能通過類的實例進行訪問,這就可以從公有接口中把接口的實現(xiàn)部分單獨分離開。如果一個類只在內(nèi)部使用該接口,而類的使用者不會直接使用到該接口,這種顯式接口成員執(zhí)行體就可以起到作用。

  2、顯式接口成員執(zhí)行體避免了接口成員之間因為同名而發(fā)生混淆。如果一個類希望對名稱和返回類型相同的接口成員采用不同的實現(xiàn)方式,這就必須要使用到顯式接口成員執(zhí)行體。如果沒有顯式接口成員執(zhí)行體,那么對于名稱和返回類型不同的接口成員,類也無法進行實現(xiàn)。

  下面的定義是無效的,因為Shape 定義時基類列表中沒有出現(xiàn)接口IComparable。

class Shape: ICloneable
{
object ICloneable.Clone( ) {…}
}
class Ellipse: Shape
{
object ICloneable.Clone( ) {…}
}

  在Ellipse 中定義ICloneable.Clone是錯誤的,因為Ellipse即使隱式地實現(xiàn)了接口ICloneable,ICloneable仍然沒有顯式地出現(xiàn)在Ellipse定義的基類列表中。

  接口成員的全權(quán)名必須對應在接口中定義的成員。如下面的例子中,Paint的顯式接口成員執(zhí)行體必須寫成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) {…}
}
 

  實現(xiàn)接口的類可以顯式實現(xiàn)該接口的成員。當顯式實現(xiàn)某成員時,不能通過類實例訪問該成員,而只能通過該接口的實例訪問該成員。顯式接口實現(xiàn)還允許程序員繼承共享相同成員名的兩個接口,并為每個接口成員提供一個單獨的實現(xiàn)。

  下面例子中同時以公制單位和英制單位顯示框的尺寸。Box類繼承 IEnglishDimensions和 IMetricDimensions兩個接口,它們表示不同的度量衡系統(tǒng)。兩個接口有相同的成員名 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

  代碼討論:如果希望默認度量采用英制單位,請正常實現(xiàn) Length 和 Width 這兩個方法,并從 IMetricDimensions 接口顯式實現(xiàn) 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、繼承接口實現(xiàn)

  接口具有不變性,但這并不意味著接口不再發(fā)展。類似于類的繼承性,接口也可以繼承和發(fā)展。

  注意:接口繼承和類繼承不同,首先,類繼承不僅是說明繼承,而且也是實現(xiàn)繼承;而接口繼承只是說明繼承。也就是說,派生類可以繼承基類的方法實現(xiàn),而派生的接口只繼承了父接口的成員方法說明,而沒有繼承父接口的實現(xiàn),其次,C#中類繼承只允許單繼承,但是接口繼承允許多繼承,一個子接口可以有多個父接口。

  接口可以從零或多個接口中繼承。從多個接口中繼承時,用":"后跟被繼承的接口名字,多個接口名之間用","分割。被繼承的接口應該是可以訪問得到的,比如從private 類型或internal 類型的接口中繼承就是不允許的。接口不允許直接或間接地從自身繼承。和類的繼承相似,接口的繼承也形成接口之間的層次結(jié)構(gòu)。

  請看下面的例子:

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方法。
一個類繼承了所有被它的基本類提供的接口實現(xiàn)程序。

  不通過顯式的實現(xiàn)一個接口,一個派生類不能用任何方法改變它從它的基本類繼承的接口映射。例如,在聲明中

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 的映射,而通過類實例和接口實例調(diào)用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( ) ;

  但是,當一個接口方法被映射到一個類中的虛擬方法,派生類就不可能覆蓋這個虛擬方法并且改變接口的實現(xiàn)函數(shù)。例如,把上面的聲明重新寫為

interface IControl {
void Paint( ) ;
}
class Control: IControl {
public virtual void Paint( ) {...}
}
class TextBox: Control {
public override void Paint( ) {...}
}

  就會看到下面的結(jié)果:

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( ); 

  由于顯式接口成員實現(xiàn)程序不能被聲明為虛擬的,就不可能覆蓋一個顯式接口成員實現(xiàn)程序。一個顯式接口成員實現(xiàn)程序調(diào)用另外一個方法是有效的,而另外的那個方法可以被聲明為虛擬的以便讓派生類可以覆蓋它。例如:

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 的實現(xiàn)程序進行特殊化。
  3、重新實現(xiàn)接口

  我們已經(jīng)介紹過,派生類可以對基類中已經(jīng)定義的成員方法進行重載。類似的概念引入到類對接口的實現(xiàn)中來,叫做接口的重實現(xiàn)(re-implementation)。繼承了接口實現(xiàn)的類可以對接口進行重實現(xiàn)。這個接口要求是在類定義的基類列表中出現(xiàn)過的。對接口的重實現(xiàn)也必須嚴格地遵守首次實現(xiàn)接口的規(guī)則,派生的接口映射不會對為接口的重實現(xiàn)所建立的接口映射產(chǎn)生任何影響。

  下面的代碼給出了接口重實現(xiàn)的例子:

interface IControl {
 void Paint( ) ;
 class Control: IControl
 void IControl.Paint( ) {…}
 class MyControl: Control, IControl
 public void Paint( ) {}

  實際上就是:Control把IControl.Paint映射到了Control.IControl.Paint上,但這并不影響在MyControl中的重實現(xiàn)。在MyControl中的重實現(xiàn)中,IControl.Paint被映射到MyControl.Paint 之上。

   在接口的重實現(xiàn)時,繼承而來的公有成員定義和繼承而來的顯式接口成員的定義參與到接口映射的過程。

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中的實現(xiàn)把接口方法映射到了Derived.F,Base.IMethods.G, Derived.IMethods.H, 還有Base.I。前面我們說過,類在實現(xiàn)一個接口時,同時隱式地實現(xiàn)了該接口的所有父接口。同樣,類在重實現(xiàn)一個接口時同時,隱式地重實現(xiàn)了該接口的所有父接口。

using System ;
interface IBase {
 void F( ) ;
}
interface IDerived: IBase {
 void G( ) ;
}
class C: IDerived {
 void IBase.F( ) {
 //對F 進行實現(xiàn)的代碼…
}
void IDerived.G( ) {
 //對G 進行實現(xiàn)的代碼…
}
}
class D: C, IDerived {
 public void F( ) {
 //對F 進行實現(xiàn)的代碼…
}
public void G( ) {
 //對G 進行實現(xiàn)的代碼…
}
}

  這里,對IDerived的重實現(xiàn)也同樣實現(xiàn)了對IBase的重實現(xiàn),把IBase.F 映射到了D.F。
  4、映射接口

  類必須為在基類表中列出的所有接口的成員提供具體的實現(xiàn)。在類中定位接口成員的實現(xiàn)稱之為接口映射(interface mapping )。

  映射,數(shù)學上表示一一對應的函數(shù)關(guān)系。接口映射的含義也是一樣,接口通過類來實現(xiàn),那么對于在接口中定義的每一個成員,都應該對應著類的一個成員來為它提供具體的實現(xiàn)。

  類的成員及其所映射的接口成員之間必須滿足下列條件:

  1、如果A和B都是成員方法,那么A和B的名稱、類型、形參表(包括參數(shù)個數(shù)和每一個參數(shù)的類型)都應該是一致的。

  2、如果A和B都是屬性,那么A和B的名稱、類型應當一致,而且A和B的訪問器也是類似的。但如果A不是顯式接口成員執(zhí)行體,A允許增加自己的訪問器。

  3、如果A和B都是時間那么A和B的名稱、類型應當一致。

  4、如果A和B都是索引指示器,那么A和B的類型、形參表(包括參數(shù)個數(shù)和每一個參數(shù)的類型)應當一致。而且A和B的訪問器也是類似的。但如果A不是顯式接口成員執(zhí)行體,A允許增加自己的訪問器。

  那么,對于一個接口成員,怎樣確定由哪一個類的成員來實現(xiàn)呢?即一個接口成員映射的是哪一個類的成員?在這里,我們敘述一下接口映射的過程。假設類C實現(xiàn)了一個接口IInterface,Member是接口IInterface中的一個成員,在定位由誰來實現(xiàn)接口成員Member,即Member的映射過程是這樣的:

  1、如果C中存在著一個顯式接口成員執(zhí)行體,該執(zhí)行體與接口IInterface 及其成員Member相對應,則由它來實現(xiàn)Member 成員。

  2、如果條件(1)不滿足,且C中存在著一個非靜態(tài)的公有成員,該成員與接口成員Member相對應,則由它來實現(xiàn)Member 成員。

  3、如果上述條件仍不滿足,則在類C定義的基類列表中尋找一個C 的基類D,用D來代替C。

  4、重復步驟1-- 3 ,遍歷C的所有直接基類和非直接基類,直到找到一個滿足條件的類的成員。

  5、如果仍然沒有找到,則報告錯誤。

  下面是一個調(diào)用基類方法來實現(xiàn)接口成員的例子。類Class2 實現(xiàn)了接口Interface1,類Class2 的基類Class1 的成員也參與了接口的映射,也就是說類Class2 在對接口Interface1進行實現(xiàn)時,使用了類Class1提供的成員方法F來實現(xiàn)接口Interface1的成員方法F:

interface Interface1 {
 void F( ) ;
}
class Class1 {
 public void F( ) { }
 public void G( ) { }
}
class Class2: Class1, Interface1 {
 new public void G( ) {}
}

  注意:接口的成員包括它自己定義的成員,而且包括該接口所有父接口定義的成員。在接口映射時,不僅要對接口定義體中顯式定義的所有成員進行映射,而且要對隱式地從父接口那里繼承來的所有接口成員進行映射。
  在進行接口映射時,還要注意下面兩點:

  1、在決定由類中的哪個成員來實現(xiàn)接口成員時,類中顯式說明的接口成員比其它成員優(yōu)先實現(xiàn)。

  2、使用Private、protected和static修飾符的成員不能參與實現(xiàn)接口映射。例如:

interface ICloneable {
 object Clone( ) ;
}
class C: ICloneable {
 object ICloneable.Clone( ) {…}
 public object Clone( ) {…}
}

  例子中成員ICloneable.Clone 稱為接口ICloneable 的成員Clone 的實現(xiàn)者,因為它是顯式說明的接口成員,比其它成員有著更高的優(yōu)先權(quán)。

  如果一個類實現(xiàn)了兩個或兩個以上名字、類型和參數(shù)類型都相同的接口,那么類中的一個成員就可能實現(xiàn)所有這些接口成員:

interface IControl {
 void Paint( ) ;
}
interface IForm {
 void Paint( ) ;
}
class Page: IControl, IForm {
 public void Paint( ) {…}

  這里,接口IControl和IForm的方法Paint都映射到了類Page中的Paint方法。當然也可以分別用顯式的接口成員分別實現(xiàn)這兩個方法:

interface IControl {
 void Paint( ) ;
}
interface IForm {
 void Paint( ) ;
}
class Page: IControl, IForm {
 public void IControl.Paint( ) {
 //具體的接口實現(xiàn)代碼
}
public void IForm.Paint( ) {
 //具體的接口實現(xiàn)代碼
}
}

  上面的兩種寫法都是正確的。但是如果接口成員在繼承中覆蓋了父接口的成員,那么對該接口成員的實現(xiàn)就可能必須映射到顯式接口成員執(zhí)行體?聪旅娴睦樱

interface IBase {
 int P { get; }
}
interface IDerived: IBase {
 new int P( ) ;
}

  接口IDerived從接口IBase中繼承,這時接口IDerived 的成員方法覆蓋了父接口的成員方法。因為這時存在著同名的兩個接口成員,那么對這兩個接口成員的實現(xiàn)如果不采用顯式接口成員執(zhí)行體,編譯器將無法分辨接口映射。所以,如果某個類要實現(xiàn)接口IDerived,在類中必須至少定義一個顯式接口成員執(zhí)行體。采用下面這些寫法都是合理的:

//一:對兩個接口成員都采用顯式接口成員執(zhí)行體來實現(xiàn)
lass C: IDerived {
 int IBase.P
 get
 { //具體的接口實現(xiàn)代碼 }
  int IDerived.P( ){
  //具體的接口實現(xiàn)代碼 }
 }
//二:對Ibase 的接口成員采用顯式接口成員執(zhí)行體來實現(xiàn)
class C: IDerived {
 int IBase.P
 get {//具體的接口實現(xiàn)代碼}
  public int P( ){
  //具體的接口實現(xiàn)代碼 }
 }
//三:對IDerived 的接口成員采用顯式接口成員執(zhí)行體來實現(xiàn)
class C: IDerived{
 public int P
 get {//具體的接口實現(xiàn)代碼}
 int IDerived.P( ){
 //具體的接口實現(xiàn)代碼}
}

  另一種情況是,如果一個類實現(xiàn)了多個接口,這些接口又擁有同一個父接口,這個父接口只允許被實現(xiàn)一次。

using System ;
interface IControl {
 void Paint( ) ;
 interface ITextBox: IControl {
 void SetText(string text) ;
}
interface IListBox: IControl {
 void SetItems(string[] items) ;
}
class ComboBox: IControl, ITextBox, IListBox {
 void IControl.Paint( ) {…}
 void ITextBox.SetText(string text) {…}
 void IListBox.SetItems(string[] items) {…}
}

  上面的例子中,類ComboBox實現(xiàn)了三個接口:IControl,ITextBox和IListBox。如果認為ComboBox不僅實現(xiàn)了IControl接口,而且在實現(xiàn)ITextBox和IListBox的同時,又分別實現(xiàn)了它們的父接口IControl。實際上,對接口ITextBox 和IListBox 的實現(xiàn),分享了對接口IControl 的實現(xiàn)。

  我們對C#的接口有了較全面的認識,基本掌握了怎樣應用C#的接口編程,但事實上,C#的不僅僅應用于.NET平臺,它同樣支持以前的COM,可以實現(xiàn)COM類到.NET類的轉(zhuǎn)換,如C#調(diào)用API。欲了解這方面的知識,請看下一節(jié)-接口轉(zhuǎn)換。