C#介面知識大全收藏

weixin_34015860發表於2014-02-15

第一節 介面慨述

 

  介面(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 元件都可以在介面轉換中使用。通常情況下,所需的調整是完全自動進行的。

 

  特別是,可以使用執行時可呼叫包裝 (RCW) 從 .NET 框架訪問 COM 元件。此包裝將 COM 元件提供的 COM 介面轉換為與 .NET 框架相容的介面。對於 OLE 自動化介面,RCW 可以從型別庫中自動生成;對於非 OLE 自動化介面,開發人員可以編寫自定義 RCW,手動將 COM 介面提供的型別對映為與 .NET 框架相容的型別。

 

  使用ComImport引用COM元件

COM Interop 提供對現有 COM 元件的訪問,而不需要修改原始元件。使用ComImport引用COM元件常包括下面 幾個方面的問題:

 

  1、建立 COM 物件。

 

  2、確定 COM 介面是否由物件實現。

 

  3、呼叫 COM 介面上的方法。

 

  4、實現可由 COM 客戶端呼叫的物件和介面。

 

  建立 COM 類包裝

 

  要使 C# 程式碼引用COM 物件和介面,需要在 C# 中包含 COM 介面的定義。完成此操作的最簡單方法是使用 TlbImp.exe(型別庫匯入程式),它是一個包括在 .NET 框架 SDK 中的命令列工具。TlbImp 將 COM 型別庫轉換為 .NET 框架後設資料,從而有效地建立一個可以從任何託管語言呼叫的託管包裝。用 TlbImp 建立的 .NET 框架後設資料可以通過 /R 編譯器選項包括在 C# 內部版本中。如果使用 Visual Studio 開發環境,則只需新增對 COM 型別庫的引用,將為您自動完成此轉換。

 

  TlbImp 執行下列轉換:

 

  1、COM coclass 轉換為具有無引數建構函式的 C# 類。

 

  2、COM 結構轉換為具有公共欄位的 C# 結構。

 

  檢查 TlbImp 輸出的一種很好的方法是執行 .NET 框架 SDK 命令列工具 Ildasm.exe(Microsoft 中間語言反彙編程式)來檢視轉換結果。

 

  雖然 TlbImp 是將 COM 定義轉換為 C# 的首選方法,但也不是任何時候都可以使用它(例如,在沒有 COM 定義的型別庫時或者 TlbImp 無法處理型別庫中的定義時,就不能使用該方法)。在這些情況下,另一種方法是使用 C# 屬性在 C# 原始碼中手動定義 COM 定義。建立 C# 源對映後,只需編譯 C# 原始碼就可產生託管包裝。

 

  執行 COM 對映需要理解的主要屬性包括:

 

  1、ComImport:它將類標記為在外部實現的 COM 類。

 

  2、Guid:它用於為類或介面指定通用唯一識別符號 (UUID)。

 

  3、InterfaceType,它指定介面是從 IUnknown 還是從 IDispatch 派生。

 

  4、PreserveSig,它指定是否應將本機返回值從 HRESULT 轉換為 .NET 框架異常。

  宣告 COM coclass

 

  COM coclass 在 C# 中表示為類。這些類必須具有與其關聯的 ComImport 屬性。下列限制適用於這些類:

 

  1、類不能從任何其他類繼承。

 

  2、類不能實現任何介面。

 

  4、類還必須具有為其設定全域性唯一識別符號 (GUID) 的 Guid 屬性。

 

  以下示例在 C# 中宣告一個 coclass:

 

// 宣告一個COM類 FilgraphManager

[ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")]

class FilgraphManager

{ }

 

  C# 編譯器將新增一個無引數建構函式,可以呼叫此建構函式來建立 COM coclass 的例項。

 

  建立 COM 物件

 

  COM coclass 在 C# 中表示為具有無引數建構函式的類。使用 new 運算子建立該類的例項等效於在 C# 中呼叫 CoCreateInstance。使用以上定義的類,就可以很容易地例項化此類:

 

 1 class MainClass
 2 
 3 {
 4 
 5 public static void Main()
 6 
 7 {
 8 
 9 FilgraphManager filg = new FilgraphManager();
10 
11 }
12 
13 }

 

  宣告 COM 介面

 

  COM 介面在 C# 中表示為具有 ComImport 和 Guid 屬性的介面。它不能在其基介面列表中包含任何介面,而且必須按照方法在 COM 介面中出現的順序宣告介面成員函式。

 

  在 C# 中宣告的 COM 介面必須包含其基介面的所有成員的宣告,IUnknown 和 IDispatch 的成員除外(.NET 框架將自動新增這些成員)。從 IDispatch 派生的 COM 介面必須用 InterfaceType 屬性予以標記。

從 C# 程式碼呼叫 COM 介面方法時,公共語言執行庫必須封送與 COM 物件之間傳遞的引數和返回值。對於每個 .NET 框架型別均有一個預設型別,公共語言執行庫將使用此預設型別在 COM 呼叫間進行封送處理時封送。例如,C# 字串值的預設封送處理是封送到本機型別 LPTSTR(指向 TCHAR 字元緩衝區的指標)。可以在 COM 介面的 C# 宣告中使用 MarshalAs 屬性重寫預設封送處理。

 

  在 COM 中,返回成功或失敗的常用方法是返回一個 HRESULT,並在 MIDL 中有一個標記為"retval"、用於方法的實際返回值的 out 引數。在 C#(和 .NET 框架)中,指示已經發生錯誤的標準方法是引發異常。

預設情況下,.NET 框架為由其呼叫的 COM 介面方法在兩種異常處理型別之間提供自動對映。

 

  返回值更改為標記為 retval 的引數的簽名(如果方法沒有標記為 retval 的引數,則為 void)。

 

  標記為 retval 的引數從方法的引數列表中剝離。

 

  任何非成功返回值都將導致引發 System.COMException 異常。

 

  此示例顯示用 MIDL 宣告的 COM 介面以及用 C# 宣告的同一介面(注意這些方法使用 COM 錯誤處理方法)。

 

  下面是介面轉換的C#程式:

 

 1 using System.Runtime.InteropServices;
 2 
 3 // 宣告一個COM介面 IMediaControl
 4 
 5 [Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"),
 6 
 7 InterfaceType(ComInterfaceType.InterfaceIsDual)]
 8 
 9 interface IMediaControl // 這裡不能列出任何基介面
10 
11 {
12 
13 void Run();
14 
15 void Pause();
16 
17 void Stop();
18 
19 void GetState( [In] int msTimeout, [Out] out int pfs);
20 
21 void RenderFile(
22 
23 [In, MarshalAs(UnmanagedType.BStr)] string strFilename);
24 
25 void AddSourceFilter(
26 
27 [In, MarshalAs(UnmanagedType.BStr)] string strFilename,
28 
29 [Out, MarshalAs(UnmanagedType.Interface)] out object ppUnk);
30 
31 [return : MarshalAs(UnmanagedType.Interface)]
32 
33 object FilterCollection();
34 
35 [return : MarshalAs(UnmanagedType.Interface)]
36 
37 object RegFilterCollection();
38 
39 void StopWhenReady();
40 
41 }

 

 

  若要防止 HRESULT 翻譯為 COMException,請在 C# 宣告中將 PreserveSig(true) 屬性附加到方法。

  下面是一個使用C# 對映媒體播放機COM 物件的程式。

 

  程式清單2 DemonCOM.cs

 

using System;

using System.Runtime.InteropServices;

namespace QuartzTypeLib

{

//宣告一個COM介面 IMediaControl,此介面來源於媒體播放機COM類

[Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"),

InterfaceType(ComInterfaceType.InterfaceIsDual)]

interface IMediaControl

{ //列出介面成員

void Run();

void Pause();

void Stop();

void GetState( [In] int msTimeout, [Out] out int pfs);

void RenderFile(

[In, MarshalAs(UnmanagedType.BStr)] string strFilename);

void AddSourceFilter(

[In, MarshalAs(UnmanagedType.BStr)] string strFilename,

[Out, MarshalAs(UnmanagedType.Interface)]

out object ppUnk);

[return: MarshalAs(UnmanagedType.Interface)]

object FilterCollection();

[return: MarshalAs(UnmanagedType.Interface)]

object RegFilterCollection();

void StopWhenReady();

}

//宣告一個COM類:

[ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")]

class FilgraphManager //此類不能再繼承其它基類或介面

{

//這裡不能有任何程式碼 ,系統自動增加一個預設的建構函式

}

}

class MainClass

{

public static void Main(string[] args)

{

//命令列引數:

if (args.Length != 1)

{

DisplayUsage();

return;

}

String filename = args[0];

if (filename.Equals("/?"))

{

DisplayUsage();

return;

}

// 宣告FilgraphManager的實類物件:

QuartzTypeLib.FilgraphManager graphManager =new QuartzTypeLib.FilgraphManager();

//宣告IMediaControl的實類物件::

QuartzTypeLib.IMediaControl mc =(QuartzTypeLib.IMediaControl)graphManager;

// 呼叫COM的方法:

mc.RenderFile(filename);

//執行檔案.

mc.Run();

//暫借停.

Console.WriteLine("Press Enter to continue.");

Console.ReadLine();

}

private static void DisplayUsage()

{ // 顯示

Console.WriteLine("媒體播放機: 播放 AVI 檔案.");

Console.WriteLine("使用方法: VIDEOPLAYER.EXE 檔名");

}

}

 

  執行示例:

 

  若要顯示影片示例 Clock.avi,請使用以下命令:

 

interop2 %windir%/clock.avi

 

  這將在螢幕上顯示影片,直到按 ENTER 鍵停止。

  在 .NET 框架程式中通過DllImport使用 Win32 API

 

  .NET 框架程式可以通過靜態 DLL 入口點的方式來訪問本機程式碼庫。DllImport 屬性用於指定包含外部方法的實現的dll 位置。

 

  DllImport 屬性定義如下:

 

 1 namespace System.Runtime.InteropServices
 2 
 3 {
 4 
 5  [AttributeUsage(AttributeTargets.Method)]
 6 
 7  public class DllImportAttribute: System.Attribute
 8 
 9  {
10 
11   public DllImportAttribute(string dllName) {...}
12 
13   public CallingConvention CallingConvention;
14 
15   public CharSet CharSet;
16 
17   public string EntryPoint;
18 
19   public bool ExactSpelling;
20 
21   public bool PreserveSig;
22 
23   public bool SetLastError;
24 
25   public string Value { get {...} }
26 
27  }
28 
29 }

 

 

  說明:

 

  1、DllImport只能放置在方法宣告上。

 

  2、DllImport具有單個定位引數:指定包含被匯入方法的 dll 名稱的 dllName 引數。

 

  3、DllImport具有五個命名引數:

 

   a、CallingConvention 引數指示入口點的呼叫約定。如果未指定 CallingConvention,則使用預設值 CallingConvention.Winapi。

 

   b、CharSet 引數指示用在入口點中的字符集。如果未指定 CharSet,則使用預設值 CharSet.Auto。

 

   c、EntryPoint 引數給出 dll 中入口點的名稱。如果未指定 EntryPoint,則使用方法本身的名稱。

 

   d、ExactSpelling 引數指示 EntryPoint 是否必須與指示的入口點的拼寫完全匹配。如果未指定 ExactSpelling,則使用預設值 false。

 

   e、PreserveSig 引數指示方法的簽名應當被保留還是被轉換。當簽名被轉換時,它被轉換為一個具有 HRESULT 返回值和該返回值的一個名為 retval 的附加輸出引數的簽名。如果未指定 PreserveSig,則使用預設值 true。

 

   f、SetLastError 引數指示方法是否保留 Win32"上一錯誤"。如果未指定 SetLastError,則使用預設值 false。

 

  4、它是一次性屬性類。

 

  5、此外,用 DllImport 屬性修飾的方法必須具有 extern 修飾符。

 

  下面是 C# 呼叫 Win32 MessageBox 函式的示例:

 

 1 using System;
 2 
 3 using System.Runtime.InteropServices;
 4 
 5 class MainApp
 6 
 7 { //通過DllImport引用user32.dll類。MessageBox來自於user32.dll類
 8 
 9  [DllImport("user32.dll", EntryPoint="MessageBox")]
10 
11  public static extern int MessageBox(int hWnd, String strMessage, String strCaption, uint uiType);
12 
13  public static void Main()
14 
15  {
16 
17   MessageBox( 0, "您好,這是 PInvoke!", ".NET", 0 );
18 
19  }
20 
21 }

 

 

  物件導向的程式語言幾乎都用到了抽象類這一概念,抽象類為實現抽象事物提供了更大的靈活性。C#也不例外, C#通過覆蓋虛介面的技術深化了抽象類的應用。欲瞭解這方面的知識,請看下一節-覆蓋虛介面

 

 

  第七節、覆蓋虛介面

 

  有時候我們需要表達一種抽象的東西,它是一些東西的概括,但我們又不能真正的看到它成為一個實體在我們眼前出現,為此物件導向的程式語言便有了抽象類的概念。C#作為一個物件導向的語言,必然也會引入抽象類這一概念。介面和抽象類使您可以建立元件互動的定義。通過介面,可以指定元件必須實現的方法,但不實際指定如何實現方法。抽象類使您可以建立行為的定義,同時提供用於繼承類的一些公共實現。對於在元件中實現多型行為,介面和抽象類都是很有用的工具。

 

  一個抽象類必須為類的基本類列表中列出的介面的所有成員提供實現程式。但是,一個抽象類被允許把介面方法對映到抽象方法中。例如

 

 

 1 interface IMethods {
 2 
 3  void F();
 4 
 5  void G();
 6 
 7 }
 8 
 9 abstract class C: IMethods
10 
11 {
12 
13  public abstract void F();
14 
15  public abstract void G();
16 
17 }

 

 

  這裡, IMethods 的實現函式把F和G對映到抽象方法中,它們必須在從C派生的非抽象類中被覆蓋。

注意顯式介面成員實現函式不能是抽象的,但是顯式介面成員實現函式當然可以呼叫抽象方法。例如

 

 

 1 interface IMethods
 2 
 3 {
 4 
 5  void F();
 6 
 7  void G();
 8 
 9 }
10 
11 abstract class C: IMethods
12 
13 {
14 
15  void IMethods.F() { FF(); }
16 
17  void IMethods.G() { GG(); }
18 
19  protected abstract void FF();
20 
21  protected abstract void GG();
22 
23 }

 

 

  這裡,從C派生的非抽象類要覆蓋FF 和 GG, 因此提供了IMethods的實際實現程式。

 

相關文章