第一節 介面慨述 介面(interface)用來定義一種程式的協定。實現介面的類或者結構要與介面的定義嚴格一致。有了這個協定,就可以拋開編程語言的限制(理論上)。介面可以從多個基介面繼承,而類或結構可以實現多個介面。介面可以包含方法、屬性、事件和索引器。介面本身不提供它所定義的成員的實現。介面只指....
第一節 介面慨述
介面(interface)用來定義一種程式的協定。實現介面的類或者結構要與介面的定義嚴格一致。有了這個協定,就可以拋開編程語言的限制(理論上)。介面可以從多個基介面繼承,而類或結構可以實現多個介面。介面可以包含方法、屬性、事件和索引器。介面本身不提供它所定義的成員的實現。介面只指定實現該介面的類或介面必須提供的成員。
介面好比一種模版,這種模版定義了對象必須實現的方法,其目的就是讓這些方法可以作為介面實例被引用。介面不能被實例化。類可以實現多個介面並且通過這些實現的介面被索引。介面變數只能索引實現該介面的類的實例。例子:
1 interface IMyExample { 2 3 string this[int index] { get ; set ; } 4 5 event EventHandler Even ; 6 7 void Find(int value) ; 8 9 string Point { get ; set ; } 10 11 } 12 13 public delegate void EventHandler(object sender, Event e) ;
上面例子中的介面包含一個索引this、一個事件Even、一個方法Find和一個屬性Point。
介面可以支持多重繼承。就像在下例中,介面"IComboBox"同時從"ITextBox"和"IListBox"繼承。
1 interface IControl { 2 3 void Paint( ) ; 4 5 } 6 7 interface ITextBox: IControl { 8 9 void SetText(string text) ; 10 11 } 12 13 interface IListBox: IControl { 14 15 void SetItems(string[] items) ; 16 17 } 18 19 interface IComboBox: ITextBox, IListBox { }
類和結構可以多重實例化介面。就像在下例中,類"EditBox"繼承了類"Control",同時從"IDataBound"和"IControl"繼承。
interface IDataBound {
void Bind(Binder b) ;
}
public class EditBox: Control, IControl, IDataBound {
public void Paint( ) ;
public void Bind(Binder b) {...}
}
在上面的代碼中,"Paint"方法從"IControl"介面而來;"Bind"方法從"IDataBound"介面而來,都以"public"的身份在"EditBox"類中實現。
說明:
1、C#中的介面是獨立於類來定義的。這與 C++模型是對立的,在 C++中介面實際上就是抽象基類。
2、介面和類都可以繼承多個介面。
3、而類可以繼承一個基類,介面根本不能繼承類。這種模型避免了 C++的多繼承問題,C++中不同基類中的實現可能出現衝突。因此也不再需要諸如虛擬繼承和顯式作用域這類複雜機制。C#的簡化介面模型有助於加快應用程式的開發。
4、一個介面定義一個只有抽象成員的引用類型。C#中一個介面實際所做的,僅僅只存在著方法標誌,但根本就沒有執行代碼。這就暗示了不能實例化一個介面,只能實例化一個派生自該介面的對象。
5、介面可以定義方法、屬性和索引。所以,對比一個類,介面的特殊性是:當定義一個類時,可以派生自多重介面,而你只能可以從僅有的一個類派生。
介面與組件
介面描述了組件對外提供的服務。在組件和組件之間、組件和客戶之間都通過介面進行交互。因此組件一旦發佈,它只能通過預先定義的介面來提供合理的、一致的服務。這種介面定義之間的穩定性使客戶應用開發者能夠構造出堅固的應用。一個組件可以實現多個組件介面,而一個特定的組件介面也可以被多個組件來實現。
組件介面必須是能夠自我描述的。這意味著組件介面應該不依賴於具體的實現,將實現和介面分離徹底消除了介面的使用者和介面的實現者之間的耦合關係,增強了信息的封裝程度。同時這也要求組件介面必須使用一種與組件實現無關的語言。目前組件介面的描述標準是IDL語言。
由於介面是組件之間的協議,因此組件的介面一旦被髮布,組件生產者就應該儘可能地保持介面不變,任何對介面語法或語義上的改變,都有可能造成現有組件與客戶之間的聯繫遭到破壞。
每個組件都是自主的,有其獨特的功能,只能通過介面與外界通信。當一個組件需要提供新的服務時,可以通過增加新的介面來實現。不會影響原介面已存在的客戶。而新的客戶可以重新選擇新的介面來獲得服務。
組件化程式設計
組件化程式設計方法繼承併發展了面向對象的程式設計方法。它把對象技術應用於系統設計,對面向對象的程式設計的實現過程作了進一步的抽象。我們可以把組件化程式設計方法用作構造系統的體繫結構層次的方法,並且可以使用面向對象的方法很方便地實現組件。
組件化程式設計強調真正的軟體可重用性和高度的互操作性。它側重於組件的產生和裝配,這兩方面一起構成了組件化程式設計的核心。組件的產生過程不僅僅是應用系統的需求,組件市場本身也推動了組件的發展,促進了軟體廠商的交流與合作。組件的裝配使得軟體產品可以採用類似於搭積木的方法快速地建立起來,不僅可以縮短軟體產品的開發周期,同時也提高了系統的穩定性和可靠性。
組件程式設計的方法有以下幾個方面的特點:
1、編程語言和開發環境的獨立性;
2、組件位置的透明性;
3、組件的進程透明性;
4、可擴充性;
5、可重用性;
6、具有強有力的基礎設施;
7、系統一級的公共服務;
C#語言由於其許多優點,十分適用於組件編程。但這並不是說C#是一門組件編程語言,也不是說C#提供了組件編程的工具。我們已經多次指出,組件應該具有與編程語言無關的特性。請讀者記住這一點:組件模型是一種規範,不管採用何種程式語言設計組件,都必須遵守這一規範。比如組裝電腦的例子,只要各個廠商為我們提供的配件規格、介面符合統一的標準,這些配件組合起來就能協同工作,組件編程也是一樣。我們只是說,利用C#語言進行組件編程將會給我們帶來更大的方便。
知道了什麼是介面,接下來就是怎樣定義介面,請看下一節--定義介面。
第二節 定義介面
從技術上講,介面是一組包含了函數型方法的數據結構。通過這組數據結構,客戶代碼可以調用組件對象的功能。
定義介面的一般形式為:
[attributes] [modifiers] interface identifier [:base-list] {interface-body}[;]
說明:
1、attributes(可選):附加的定義性信息。
2、modifiers(可選): 允許使用的修飾符有 new 和四個訪問修飾符。分別是:new、public、protected、internal、 private。在一個介面定義中同一修飾符不允許出現多次,new 修飾符只能出現在嵌套介面中,表示覆蓋了繼承而來的同名成員。The public, protected, internal, and private 修飾符定義了對介面的訪問許可權。
3、指示器和事件。
4、identifier:介面名稱。
5、base-list(可選):包含一個或多個顯式基介面的列表,介面間由逗號分隔。
6、interface-body:對介面成員的定義。
7、介面可以是命名空間或類的成員,並且可以包含下列成員的簽名: 方法、屬性、索引器 。
8、一個介面可從一個或多個基介面繼承。
介面這個概念在C#和Java中非常相似。介面的關鍵詞是interface,一個介面可以擴展一個或者多個其他介面。按照慣例,介面的名字以大寫字母"I"開頭。下麵的代碼是C#介面的一個例子,它與Java中的介面完全一樣:
interface IShape {
void Draw ( ) ;
}
如果你從兩個或者兩個以上的介面派生,父介面的名字列表用逗號分隔,如下麵的代碼所示:
interface INewInterface: IParent1, IParent2 { }
然而,與Java不同,C#中的介面不能包含域(Field)。另外還要註意,在C#中,介面內的所有方法預設都是公用方法。在Java中,方法定義可以帶有public修飾符(即使這並非必要),但在C#中,顯式為介面的方法指定public修飾符是非法的。例如,下麵的C#介面將產生一個編譯錯誤。
interface IShape { public void Draw( ) ; }
下麵的例子定義了一個名為IControl 的介面,介面中包含一個成員方法Paint:
interface IControl {
void Paint( ) ;
}
在下例中,介面 IInterface從兩個基介面 IBase1 和 IBase2 繼承:
interface IInterface: IBase1, IBase2 {
void Method1( ) ;
void Method2( ) ;
}
介面可由類實現。實現的介面的標識符出現在類的基列表中。例如:
1 class Class1: Iface1, Iface2 { 2 3 // class 成員。 4 // http://www.cnblogs.com/roucheng/ 5 }
類的基列表同時包含基類和介面時,列表中首先出現的是基類。例如:
class ClassA: BaseClass, Iface1, Iface2 {
// class成員。
}
以下的代碼段定義介面IFace,它只有一個方法:
interface IFace {
void ShowMyFace( ) ;
}
不能從這個定義實例化一個對象,但可以從它派生一個類。因此,該類必須實現ShowMyFace抽象方法:
1 class CFace:IFace 2 3 { 4 5 public void ShowMyFace( ) { 6 7 Console.WriteLine(" implementation " ) ; 8 9 } 10 11 }
基介面
一個介面可以從零或多個介面繼承,那些被稱為這個介面的顯式基介面。當一個介面有比零多的顯式基介面時,那麼在介面的定義中的形式為,介面標識符後面跟著由一個冒號":"和一個用逗號","分開的基介面標識符列表。
介面基:
:介面類型列表說明:
1、一個介面的顯式基介面必須至少同介面本身一樣可訪問。例如,在一個公共介面的基介面中指定一個私有或內部的介面是錯誤的。
2、一個介面直接或間接地從它自己繼承是錯誤的。
3、介面的基介面都是顯式基介面,並且是它們的基介面。換句話說,基介面的集合完全由顯式基介面和它們的顯式基介面等等組成。在下麵的例子中
1 interface IControl { 2 3 void Paint( ) ; 4 5 } 6 7 interface ITextBox: IControl { 8 9 void SetText(string text) ; 10 11 } 12 13 interface IListBox: IControl { 14 15 void SetItems(string[] items) ; 16 17 } 18 19 interface IComboBox: ITextBox, IListBox { }
IComboBox 的基介面是IControl, ITextBox, 和 IlistBox。
4、一個介面繼承它的基介面的所有成員。換句話說,上面的介面 IComboBox 就像Paint一樣繼承成員SetText 和 SetItems。
5、一個實現了介面的類或結構也隱含地實現了所有介面的基介面。
介面主體
一個介面的介面主體定義介面的成員。
interface-body:
{ interface-member-declarationsopt }
定義介面主要是定義介面成員,請看下一節--定義介面成員。
第三節 定義介面成員
介面可以包含一個和多個成員,這些成員可以是方法、屬性、索引指示器和事件,但不能是常量、域、操作符、構造函數或析構函數,而且不能包含任何靜態成員。介面定義創建新的定義空間,並且介面定義直 接包含的介面成員定義將新成員引入該定義空間。
說明:
1、介面的成員是從基介面繼承的成員和由介面本身定義的成員。
2、介面定義可以定義零個或多個成員。介面的成員必須是方法、屬性、事件或索引器。介面不能包含常數、欄位、運算符、實例構造函數、析構函數或類型,也不能包含任何種類的靜態成員。
3、定義一個介面,該介面對於每種可能種類的成員都包含一個:方法、屬性、事件和索引器。
4、介面成員預設訪問方式是public。介面成員定義不能包含任何修飾符,比如成員定義前不能加abstract,public,protected,internal,private,virtual,override 或static 修飾符。
5、介面的成員之間不能相互同名。繼承而來的成員不用再定義,但介面可以定義與繼承而來的成員同名的成員,這時我們說介面成員覆蓋了繼承而來的成員,這不會導致錯誤,但編譯器會給出一個警告。關閉警告提示的方式是在成員定義前加上一個new關鍵字。但如果沒有覆蓋父介面中的成員,使用new 關鍵字會導致編譯器發出警告。
6、方法的名稱必須與同一介面中定義的所有屬性和事件的名稱不同。此外,方法的簽名必須與同一介面中定義的所有其他方法的簽名不同。
7、屬性或事件的名稱必須與同一介面中定義的所有其他成員的名稱不同。
8、一個索引器的簽名必須區別於在同一介面中定義的其他所有索引器的簽名。
9、介面方法聲明中的屬性(attributes), 返回類型(return-type), 標識符(identifier), 和形式參數列表(formal-parameter-lis)與一個類的方法聲明中的那些有相同的意義。一個介面方法聲明不允許指定一個方法主體,而聲明通常用一個分號結束。
10、介面屬性聲明的訪問符與類屬性聲明的訪問符相對應,除了訪問符主體通常必須用分號。因此,無論屬性是讀寫、只讀或只寫,訪問符都完全確定。
11、介面索引聲明中的屬性(attributes), 類型(type), 和形式參數列表 (formal-parameter-list)與類的索引聲明的那些有相同的意義。
下麵例子中介面IMyTest包含了索引指示器、事件E、 方法F、 屬性P 這些成員:
1 interface IMyTest{ 2 3 string this[int index] { get; set; } 4 5 event EventHandler E ; 6 7 void F(int value) ; 8 9 string P { get; set; } 10 11 } 12 13 public delegate void EventHandler(object sender, EventArgs e) ;
下麵例子中介面IStringList包含每個可能類型成員的介面:一個方法,一個屬性,一個事件和一個索引。
public delegate void StringListEvent(IStringList sender);
public interface IStringList
{
void Add(string s);
int Count { get; }
event StringListEvent Changed;
string this[int index] { get; set; }
}
介面成員的全權名
使用介面成員也可採用全權名(fully qualified name)。介面的全權名稱是這樣構成的。介面名加小圓點"." 再跟成員名比如對於下麵兩個介面:
interface IControl {
void Paint( ) ;
}
interface ITextBox: IControl {
void GetText(string text) ;
}
其中Paint 的全權名是IControl.Paint,GetText的全權名是ITextBox. GetText。當然,全權名中的成員名稱必須是在介面中已經定義過的,比如使用ITextBox.Paint.就是不合理的。
如果介面是名字空間的成員,全權名還必須包含名字空間的名稱。
namespace System
{
public interface IDataTable {
object Clone( ) ;
}
}
那麼Clone方法的全權名是System. IDataTable.Clone。
定義好了介面,接下來就是怎樣訪問介面,請看下一節--訪問介面
第四節、訪問介面
對介面成員的訪問
對介面方法的調用和採用索引指示器訪問的規則與類中的情況也是相同的。如果底層成員的命名與繼承而來的高層成員一致,那麼底層成員將覆蓋同名的高層成員。但由於介面支持多繼承,在多繼承中,如果兩個父介面含有同名的成員,這就產生了二義性(這也正是C#中取消了類的多繼承機制的原因之一),這時需要進行顯式的定義:
1 using System ; 2 3 interface ISequence { 4 5 int Count { get; set; } 6 7 } 8 9 interface IRing { 10 11 void Count(int i) ; 12 13 } 14 // http://www.cnblogs.com/roucheng/ 15 interface IRingSequence: ISequence, IRing { } 16 17 class CTest { 18 19 void Test(IRingSequence rs) { 20 21 //rs.Count(1) ; 錯誤, Count 有二義性 22 23 //rs.Count = 1; 錯誤, Count 有二義性 24 25 ((ISequence)rs).Count = 1; // 正確 26 27 ((IRing)rs).Count(1) ; // 正確調用IRing.Count 28 29 } 30 31 }
上面的例子中,前兩條語句rs .Count(1)和rs .Count = 1會產生二義性,從而導致編譯時錯誤,因此必須顯式地給rs 指派父介面類型,這種指派在運行時不會帶來額外的開銷。
再看下麵的例子:
1 using System ; 2 3 interface IInteger { 4 5 void Add(int i) ; 6 7 } 8 9 interface IDouble { 10 11 void Add(double d) ; 12 13 } 14 15 interface INumber: IInteger, IDouble {} 16 17 class CMyTest { 18 19 void Test(INumber Num) { 20 21 // Num.Add(1) ; 錯誤 22 23 Num.Add(1.0) ; // 正確 24 25 ((IInteger)n).Add(1) ; // 正確 26 27 ((IDouble)n).Add(1) ; // 正確 28 29 } 30 31 }
調用Num.Add(1) 會導致二義性,因為候選的重載方法的參數類型均適用。但是,調用Num.Add(1.0) 是允許的,因為1.0 是浮點數參數類型與方法IInteger.Add()的參數類型不一致,這時只有IDouble.Add 才是適用的。不過只要加入了顯式的指派,就決不會產生二義性。
介面的多重繼承的問題也會帶來成員訪問上的問題。例如:
1 interface IBase { 2 3 void FWay(int i) ; 4 5 } 6 7 interface ILeft: IBase { 8 9 new void FWay (int i) ; 10 11 } 12 13 interface IRight: IBase 14 15 { void G( ) ; } 16 17 interface IDerived: ILeft, IRight { } 18 19 class CTest { 20 21 void Test(IDerived d) { 22 23 d. FWay (1) ; // 調用ILeft. FWay http://www.cnblogs.com/roucheng/ 24 25 ((IBase)d). FWay (1) ; // 調用IBase. FWay 26 27 ((ILeft)d). FWay (1) ; // 調用ILeft. FWay 28 29 ((IRight)d). FWay (1) ; // 調用IBase. FWay 30 31 } 32 33 }
上例中,方法IBase.FWay在派生的介面ILeft中被Ileft的成員方法FWay覆蓋了。所以對d. FWay (1)的調用實際上調用了。雖然從IBase-> IRight-> IDerived這條繼承路徑上來看,ILeft.FWay方法是沒有被覆蓋的。我們只要記住這一點:一旦成員被覆蓋以後,所有對其的訪問都被覆蓋以後的成員"攔截"了。
類對介面的實現
前面我們已經說過,介面定義不包括方法的實現部分。介面可以通過類或結構來實現。我們主要講述通過類來實現介面。用類來實現介面時,介面的名稱必須包含在類定義中的基類列表中。
下麵的例子給出了由類來實現介面的例子。其中ISequence 為一個隊列介面,提供了向隊列尾部添加對象的成員方法Add( ),IRing 為一個迴圈表介面,提供了向環中插入對象的方法Insert(object obj),方法返回插入的位置。類RingSquence 實現了介面ISequence 和介面IRing。
1 using System ; 2 3 interface ISequence { 4 5 object Add( ) ; 6 7 } 8 9 interface ISequence { 10 11 object Add( ) ; 12 13 } 14 15 interface IRing { 16 17 int Insert(object obj) ; 18 19 } 20 21 class RingSequence: ISequence, IRing 22 23 { 24 25 public object Add( ) {…} 26 27 public int Insert(object obj) {…} 28 29 }
如果類實現了某個介面,類也隱式地繼承了該介面的所有父介面,不管這些父介面有沒有在類定義的基類表中列出。看下麵的例子:
1 using System ; 2 3 interface IControl { 4 5 void Paint( ); 6 7 } 8 9 interface ITextBox: IControl { 10 11 void SetText(string text); 12 13 } 14 15 interface IListBox: IControl { 16 17 void SetItems(string[] items); 18 19 } 20 21 interface IComboBox: ITextBox, IListBox { }
這裡, 介面IcomboBox繼承了ItextBox和IlistBox。類TextBox不僅實現了介面ITextBox,還實現了介面ITextBox 的父介面IControl。
前面我們已經看到,一個類可以實現多個介面。再看下麵的例子:
1 interface IDataBound { 2 3 void Bind(Binder b); 4 5 } 6 7 public class EditBox: Control, IControl, IDataBound { 8 9 public void Paint( ); 10 11 public void Bind(Binder b) {...} 12 13 }
類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介面來調用。
1 class Test { 2 3 static void Main( ) { 4 5 EditBox editbox = new EditBox( ); 6 7 editbox.Paint( ); //錯誤: EditBox 沒有Paint 事件 8 9 IControl control = editbox; 10 11 control.Paint( ); // 調用 EditBox的Paint事件 12 13 } 14 15 }
上例中,類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) {…}
}
顯式介面成員只能通過介面調用。例如:
1 class CTest { 2 3 static void Main( ) { 4 5 EditBox editbox = new EditBox( ) ; 6 7 editbox.Paint( ) ; //錯誤:不同的方法 8 9 IControl control = editbox; 10 11 control.Paint( ) ; //調用 EditBox的Paint方法 12 13 } 14 15 }
上述代碼中對editbox.Paint( )的調用是錯誤的,因為editbox 本身並沒有提供這一方法。control.Paint( )是正確的調用方式。
註釋:介面本身不提供所定義的成員的實現,它僅僅說明這些成員,這些成員必須依靠實現介面的類或其它介面的支持。
知道了怎樣訪問介面,我們還要知道怎樣實現介面,要實現C#的介面,請看下一節-實現介面
第五節、實現介面
1、顯式實現介面成員
為了實現介面,類可以定義顯式介面成員執行體(Explicit interface member implementations)。顯式介面成員執行體可以是一個方法、一個屬性、一個事件或者是一個索引指示器的定義,定義與該成員對應的全權名應保持一致。
1 using System ; 2 3 interface ICloneable { 4 5 object Clone( ) ; 6 7 } 8 9 interface IComparable { 10 11 int CompareTo(object other) ; 12 13 } 14 15 class ListEntry: ICloneable, IComparable { 16 17 object ICloneable.Clone( ) {…} 18 19 int IComparable.CompareTo(object other) {…} 20 21 } 22 23
上面的代碼中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。
1 class Shape: ICloneable 2 3 { 4 5 object ICloneable.Clone( ) {…} 6 7 } 8 9 class Ellipse: Shape 10 11 { 12 13 object ICloneable.Clone( ) {…} 14 15 }
在Ellipse 中定義ICloneable.Clone是錯誤的,因為Ellipse即使隱式地實現了介面ICloneable,ICloneable仍然沒有顯式地出現在Ellipse定義的基類列表中。
介面成員的全權名必須對應在介面中定義的成員。如下麵的例子中,Paint的顯式介面成員執行體必須寫成IControl.Paint。
1 using System ; 2 3 interface IControl 4 5 { 6 7 void Paint( ) ; 8 9 } 10 11 interface ITextBox: IControl 12 13 { 14 15 void SetText(string text) ; 16 17 } 18 19 class TextBox: ITextBox 20 21 { 22 23 void IControl.Paint( ) {…} 24 25 void ITextBox.SetText(string text) {…} 26 27 }
實現介面的類可以顯式實現該介面的成員。當顯式實現某成員時,不能通過類實例訪問該成員,而只能通過該介面的實例訪問該成員。顯式介面實現還允許程式員繼承共用相同成員名的兩個介面,併為每個介面成員提供一個單獨的實現。
下麵例子中同時以公制單位和英制單位顯示框的尺寸。Box類繼承 IEnglishDimensions和 IMetricDimensions兩個介面,它們表示不同的度量衡系統。兩個介面有相同的成員名 Length 和 Width。
程式清單1 DemonInterface.cs
1 interface IEnglishDimensions { 2 3 float Length ( ) ; 4 5 float Width ( ) ; 6 7 } 8 9 interface IMetricDimensions { 10 11 float Length ( ) ; 12 13 float Width ( ) ; 14 15 } 16 17 class Box : IEnglishDimensions, IMetricDimensions { 18 19 float lengthInches ; 20 21 float widthInches ; 22 23 public Box(float length, float width) { 24 25 lengthInches = length ; 26 27 widthInches = width ; 28 29 } 30 31 float IEnglishDimensions.Length( ) { 32 33 return lengthInches ; 34 35 } 36 37 float IEnglishDimensions.Width( ) { 38 39 return widthInches ; 40 41 } 42 43 float IMetricDimensions.Length( ) { 44 45 return lengthInches * 2.54f ; 46 47 } 48 49 float IMetricDimensions.Width( ) { 50 51 return widthInches * 2.54f ; 52 53 } 54 55 public static void Main( ) { 56 57 //定義一個實類對象 "myBox":: 58 59 Box myBox = new Box(30.0f, 20.0f); 60 61 // 定義一個介面" eDimensions":: 62 63 IEnglishDimensions eDimensions = (IEnglishDimensions) myBox; 64 65 IMetricDimensions mDimensions = (IMetricDimensions) myBox; 66 67 // 輸出: 68 69 System.Console.WriteLine(" Length(in): {0}", eDimensions.Length( )); 70 71 System.Console.WriteLine(" Width (in): {0}", eDimensions.Width( )); 72 73 System.Console.WriteLine(" Length(cm): {0}", mDimensions.Length( )); 74 75 System.Console.WriteLine(" Width (cm): {0}", mDimensions.Width( )); 76 77 } 78 79 }
輸出:Length(in): 30,Width (in): 20,Length(cm): 76.2,Width (cm): 50.8
代碼討論:如果希望預設度量採用英制單位,請正常實現 Length 和 Width 這兩個方法,並從 IMetricDimensions 介面顯式實現 Length 和 Width 方法:
1 public float Length( ) { 2 3 return lengthInches ; 4 5 } 6 7 public float Width( ){ 8 9 return widthInches; 10 11 } 12 13 float IMetricDimensions.Length( ) { 14 15 return lengthInches * 2.54f ; 16 17 } 18 19 float IMetricDimensions.Width( ) { 20 21 return widthInches * 2.54f ; 22 23 }
這種情況下,可以從類實例訪問英制單位,而從介面實例訪問公制單位:
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 類型的介面中繼承就是不允許的。介面不允許直接或間接地從自身繼承。和類的繼承相似,介面的繼承也形成介面之間的層次結構。
請看下麵的例子:
1 using System ; 2 3 interface IControl { 4 5 void Paint( ) ; 6 7 } 8 9 interface ITextBox: IControl { 10 11 void SetText(string text) ; 12 13 } 14 15 interface IListBox: IControl { 16 17 void SetItems(string[] items) ; 18 19 } 20 21 interface IComboBox: ITextBox, IListBox { }
對一個介面的繼承也就繼承了介面的所有成員,上面的例子中介面ITextBox和IListBox都從介面IControl中繼承,也就繼承了介面IControl的Paint方法。介面IComboBox從介面ITextBox和IListBox中繼承,因此它應該繼承了介面ITextBox的SetText方法和IListBox的SetItems方法,還有IControl的Paint方法。
一個類繼承了所有被它的基本類提供的介面實現程式。
不通過顯式的實現一個介面,一個派生類不能用任何方法改變它從它的基本類繼承的介面映射。例如,在聲明中
1 interface IControl { 2 3 void Paint( ); 4 5 } 6 7 class Control: IControl { 8 9 public void Paint( ) {...} 10 11 } 12 13 class TextBox: Control { 14 15 new public void Paint( ) {...} 16 17 }
TextBox 中的方法Paint 隱藏了Control中的方法Paint ,但是沒有改變從Control.Paint 到IControl.Paint 的映射,而通過類實例和介面實例調用Paint將會有下麵的影響
1 Control c = new Control( ) ; 2 3 TextBox t = new TextBox( ) ; 4 5 IControl ic = c ; 6 7 IControl it = t ; 8 9 c.Paint( ) ; // 影響Control.Paint( ) ; 10 11 t.Paint( ) ; // 影響TextBox.Paint( ) ; 12 13 ic.Paint( ) ; // 影響Control.Paint( ) ; 14 15 it.Paint( ) ; // 影響Control.Paint( ) ;
但是,當一個介面方法被映射到一個類中的虛擬方法,派生類就不可能覆蓋這個虛擬方法並且改變介面的實現函數。例如,把上面的聲明重新寫為
1 interface IControl { 2 3 void Paint( ) ; 4 5 } 6 7 class Control: IControl { 8 9 public virtual void Paint( ) {...} 10 11 } 12 13 class TextBox: Control { 14 15 public override void Paint( ) {...} 16 17 }
就會看到下麵的結果:
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( );
由於顯式介面成員實現程式不能被聲明為虛擬的,就不可能覆蓋一個顯式介面成員實現程式。一個顯式介面成員實現程式調用另外一個方法是有效的,而另外的那個方法可以被聲明為虛擬的以便讓派生類可以覆蓋它。例如:
1 interface IControl { 2 3 void Paint( ) ; 4 5 } 6 7 class Control: IControl { 8 9 void IControl.Paint( ) { PaintControl( ); } 10 11 protected virtual void PaintControl( ) {...} 12 13 } 14 15 class TextBox: Control { 16 17 protected override void PaintControl( ) {...} 18 19 }
這裡,從Control 繼承的類可以通過覆蓋方法PaintControl 來對IControl.Paint 的實現程式進行特殊化。
3、重新實現介面
我們已經介紹過,派生類可以對基類中已經定義的成員方法進行重載。類似的概念引入到類對介面的實現中來,叫做介面的重實現(re-implementation)。繼承了介面實現的類可以對介面進行重實現。這個介面要求是在類定義的基類列表中出現過的。對介面的重實現也必須嚴格地遵守首次實現介面的規則,派生的介面映射不會對為介面的重實現所建立的介面映射產生任何影響。
下麵的代碼給出了介面重實現的例子:
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 之上。
在介面的重實現時,繼承而來的公有成員定義和繼承而來的顯式介面成員的定義參與到介面映射的過程。
1 using System ; 2 3 interface IMethods { 4 5 void F( ) ; 6 7 void G( ) ; 8 9 void H( ) ; 10 11 void I( ) ; 12 13 } 14 15 class Base: IMethods { 16 17 void IMethods.F( ) { } 18 19 void IMethods.G( ) { } 20 21 public void H( ) { } 22 23 public void I( ) { } 24 25 } 26 27 class Derived: Base, IMethods { 28 29 public void F( ) { } 30 31 void IMethods.H( ) { } 32 33 }
這裡,介面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 進行實現的代碼… http://www.cnblogs.com/roucheng/ } }
這裡,對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 成員。
2、如果條件(1)不滿足,且C中存在著一個非靜態的公有成員,該成員與介面成員Member相對應,則由它來實現Member 成員。
3、如果上述條件仍不滿足,則在類C定義的基類列表中尋找一個C 的基類D,用D來代替C。
4、重覆步驟1-- 3 ,遍歷C的所有直接基類和非直接基類,直到找到一個滿足條件的類的成員。
5、如果仍然沒有找到,則報告錯誤。
下麵是一個調用基類方法來實現介面成員的例子。類Class2 實現了介面Interface1,類Class2 的基類Class1 的成員也參與了介面的映射,也就是說類Class2 在對介面Interface1進行實現時,使用了類Class1提供的成員方法F來實現介面Interface1的成員方法F:
interface Interface1 {
void F( ) ;
}
class Class1 {
public void F( ) { }
public void G( ) { }
}
class Class2: Class1, Interface1 {
new public void G( ) {}
}
註意:介面的成員包括它自己定義的成員,而且包括該介面所有父介面定義的成員。在介面映射時,不僅要對介面定義體中顯式定義的所有成員進行映射,而且要對隱式地從父介面那裡繼承來的所有介面成員進行映射。
在進行介面映射時,還要註意下麵兩點:
1、在決定由類中的哪個成員來實現介面成員時,類中顯式說明的介面成員比其它成員優先實現。
2、使用Private、protected和static修飾符的成員不能參與實現介面映射。例如:
1 interface ICloneable { 2 3 object Clone( ) ; 4 5 } 6 7 class C: ICloneable { 8 9 object ICloneable.Clone( ) {…} 10 11 public object Clone( ) {…} 12 13 }
例子中成員ICloneable.Clone 稱為介面ICloneable 的成員Clone 的實現者,因為它是顯式說明的介面成員,比其它成員有著更高的優先權。
如果一個類實現了兩個或兩個以上名字、類型和參數類型都相同的介面,那麼類中的一個成員就可能實現所有這些介面成員:
1 interface IControl { 2 3 void Paint( ) ; 4 5 } 6 7 interface IForm { 8 9 void Paint( ) ; 10 11 } 12 13 class Page: IControl, IForm { 14 15 public void Paint( ) {…} 16 17 }
這裡,介面IControl和IForm的方法Paint都映射到了類Page中的Paint方法。當然也可以分別用顯式的介面成員分別實現這兩個方法:
1 interface IControl { 2 3 void Paint( ) ; 4 5 } 6 7 interface IForm { 8 9 void Paint( ) ; 10 11 } 12 13 class Page: IControl, IForm { 14 15 public void IControl.Paint( ) { 16 17 //具體的介面實現代碼 18 19 } 20 21 public void IForm.Paint( ) { 22 23 //具體的介面實現代碼 http://roucheng.cnblogs.com/ 24 25 } 26 27 }
上面的兩種寫法都是正確的。但是如果介面成員在繼承中覆蓋了父介面的成員,那麼對該介面成員的實現就可能必須映射到顯式介面成員執行體。看下麵的例子:
1 interface IBase { 2 3 int P { get; } 4 5 } 6 7 interface IDerived: IBase { 8 9 new int P( ) ; 10 11 }
介面IDerived從介面IBase中繼承,這時介面IDerived 的成員方法覆蓋了父介面的成員方法。因為這時存在著同名的兩個介面成員,那麼對這兩個介面成員的實現如果不採用顯式介面成員執行體,編譯器將無法分辨介面映射。所以,如果某個類要實現介面IDerived,在類中必須至少定義一個顯式介面成員執行體。採用下麵這些寫法都是合理的:
//一:對兩個介面成員都採用顯式介面成員執行體來實現
1 class C: IDerived { 2 3 int IBase.P 4 5 get 6 7 { //具體的介面實現代碼 } 8 9 int IDerived.P( ){ 10 11 //具體的介面實現代碼 } 12 13 }
//二:對Ibase 的介面成員採用顯式介面成員執行體來實現
1 class C: IDerived { 2 3 int IBase.P 4 5 get {//具體的介面實現代碼} 6 7 public int P( ){ 8 9 //具體的介面實現代碼 } 10 11 }
//三:對IDerived 的介面成員採用顯式介面成員執行體來實現
1 class C: IDerived{ 2 3 public int P 4 5 get {//具體的介面實現代碼} 6 7 int IDerived.P( ){ 8 9 //具體的介面實現代碼} 10 11 }
另一種情況是,如果一個類實現了多個介面,這些介面又擁有同一個父介面,這個父介面只允許被實現一次。
1 using System ; 2 3 interface IControl { 4 5 void Paint( ) ; 6 7 interface ITextBox: IControl { 8 9 void SetText(string text) ; 10 11 } 12 13 interface IListBox: IControl { 14 15 void SetItems(string[] items) ; 16 17 } 18 19 class ComboBox: IControl, ITextBox, IListBox { 20 21 void IControl.Paint( ) {…} 22 23 void ITextBox.SetText(string text) {…} 24 25 void IListBox.SetItems(string[] items) {…} 26 27 }
上面的例子中,類ComboBox實現了三個介面:IControl,ITextBox和IListBox。如果認為ComboBox不僅實現了IControl介面,而且在實現ITextBox和IListBox的同時,又分別實現了它們的父介面IControl。實際上,對介面ITextBox 和IListBox 的實現,分享了對介面IControl 的實現。
我們對C#的介面有了較全面的認識,基本掌握了怎樣應用C#的介面編程,但事實上,C#的不僅僅應用於.NET平臺,它同樣支持以前的COM,可以實現COM類到.NET類的轉換,如C#調用API。欲瞭解這方面的知識,請看下一節-介面轉換。
第六節、介面轉換
C#中不僅支持.Net 平臺,而且支持COM平臺。為了支持 COM和.Net,C# 包含一種稱為屬性的獨特語言特性。一個屬性實際上就是一個 C# 類,它通過修飾源代碼來提供元信息。屬性使 C# 能夠支持特定的技術,如 COM 和 .Net,而不會幹擾語言規範本身。C# 提供將COM介面轉換為 C#介面的屬性類。另一些屬性類將 COM類轉換為C# 類。執行這些轉換不需要任何 IDL 或類工廠。
現在部署的任何COM 組件都可以在介面轉換