第一節 接口慨述
接口(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 }