本文轉自:http://www.cnblogs.com/godwar/archive/2008/01/17/1042481.html
看到標題,大部分會說“運行時建立對象”那不是小兒科,就這樣:
Dim newButton As Button = New Button()
newButton.Name = "Button1"
這的確是在運行時建立了一個按鈕。不過若需按照使用者要求建立按鈕、複選框或者單選框怎麼辦,好像也好辦:
Dim newControl As Control
Select Case userSelection
Case "按鈕"
newControl = New Button()
Case "複選框"
newControl = New CheckBox()
....
End Select
如果使用者需要的是Windows.Forms裡面的數十種控制項,那麼你的Select語句也要寫數十行嗎?我當然不是想要做這種刁難的使用者,但是需求總是多種多樣的,若有一種方法能夠在運行時任意指定對象的建立類型,甚至是用表示類型的名字的字串建立所需的對象,該有多麼方便。.net Framwork的反射機制給我們帶來瞭解決問題的方法。這裡,若只需要建立一般的對象,我們可以通過System.Activator來實現,而較複雜的我們可以通過擷取構造方法來實現。
反射Reflection是.net中重要機制,很多人已經介紹過反射,我們來簡單複習一下。通過反射,可以在運行時獲得.net中每一個類型(包括類、結構、委派、介面、枚舉)的成員,包括方法、屬性、事件以及建構函式等,還可以獲得每個成員的名稱、限定符和參數等,有了反射,就可以對每一個類型了如指掌。如果獲得了建構函式的資訊,就可以直接建立對象,即使這個對象的類型在編譯的時候還不知道。
在完成運行時建立控制項這一任務前,我們先看一個簡單的例子,建立一個名為VBAppliction的Windows程式,添加一個新檔案,輸入一個新類:
Public Class MyClassTest
Private MyField As String
Public Sub New()
MyField = "Hi!"
End Sub
Public Sub Hello()
Console.WriteLine(MyField)
End Sub
End Class
然後加給表單入一個新按鈕,輸入以下事件代碼:
''方法一
Dim t As Type = GetType(MyClassTest)
o = System.Activator.CreateInstance(t)
o.Hello()
第一行GetType(MyClassTest)函數就已經獲得了我們建立的類的類型對象(C#中使用typeof函數)。接下來,我們用了System.Activator類的一個靜態方法CreateInstance建立出對象執行個體,並將對象引用賦給o。Activator是一個用來在建立本地或遠程對象的工具。運行這個程式,我們可以從Commond Window(命令視窗,一般在調試狀態IDE的右下方)看到WriteLine函數啟動並執行結果,可以看到正確建立的對象。
如果我們用的類具有比較複雜的建構函式,還可以使用建構函式建立所需的對象,代碼如下:
''方法二
Dim t As Type = GetType(MyClassTest)
Dim c As System.Reflection.ConstructorInfo
Dim types() As Type
ReDim types(-1)
c = t.GetConstructor(Reflection.BindingFlags.Instance _
Or Reflection.BindingFlags.Public, _
Nothing, Reflection.CallingConventions.HasThis, types, Nothing)
Dim params() As Object
ReDim params(-1)
o.Hello()
這裡我們建立一個System.Reflection.ConstructorInfo的對象,通過它可以獲得類構造方法的資訊。我們用的是Type類的GetConstructor方法來搜尋可用的構造方法。
需要解釋一下的是types()數組,這個數組是搜尋構造方法所用的參數類型表。我們的類的構造方法沒有參數,所以需要一個空但不為Nothing(C#中為null)的數組,ReDim types(-1)就是建立這種數組的語句,在C#中可寫作:
types = new Type[0];
若構造方法是這樣:
Public Sub New(ByVal A As Integer, B As String)
那麼相應的types數組就應該是
Dim types(1) As Type
types(0) = GetType(Int32)
types(1) = GetType(String)
Reflection.BindingFlags.Instance和Reflection.BindingFlags.Public是一個位屏蔽,是指定搜尋方式的選項。
params()數組是構造方法的參數內容表,同樣因沒有參數,我們使用ReDim -1的文法。
Invoke方法執行了構造方法,建立出對象執行個體。
現在我們回到第一種實現方法,將代碼改一下,將
Dim t As Type = GetType(MyClassTest)
改為
Dim t As Type = Type.GetType("VBApplication.MyClassTest")
啟動並執行結果沒有改變,這就是說,我們實現了從字串建立對象!不過這裡GetType方法的使用有限制,具體我們後面再說。現在就可以實現我們的願望:動態建立控制項。通過上面的知識,我們很容易寫出一個動態建立視窗控制項的子程式:
Private Function CreateNewControls(ByVal targetControls As Control.ControlCollection, ByVal ctlName As String, ByVal ctlType As Type, ByVal ctlSize As Drawing.Size, ByVal ctlLocation As Drawing.Point) As Control
Dim toCreate As Control
toCreate = CType(System.Activator.CreateInstance(ctlType), Control)
toCreate.Name = ctlName
toCreate.Size = ctlSize
toCreate.Location = ctlLocation
targetControls.Add(toCreate)
Return toCreate
End Function
那一句較長的語句中包含了上一個例子中的所有內容。如果用C#書寫,則可以寫成
toCreate = (Control)System.Activator.CreateInstance(ctlType);
我們將按鈕的事件程序改成:
Dim c As Control = Me.CreateNewControls1(Me.Controls, "Control1", GetType(CheckBox), New Size(168, 40), New Point(64, 176))
c.Text = "New Creation"
現在,單擊一下按鈕,就可以看到一個新的CheckBox出現在視窗上,標題為New Creation,而且,如果編寫了事件程序,還可以為建立的控制項添加事件響應。
看來一切都達到目的了?注意這一句GetType(CheckBox)還是使用了類名的字面表示,無法達到用字串建立對象的功能。如果我們把這一句改成Type.GetType("System.Windows.Forms.CheckBox")行不行?嗯,實驗一下,呵呵,出錯了。為什麼會這樣?Type.GetType()方法從字串獲得類型僅限於corlib中的類型或者工程內部的類型,如果是來自於外部的程式集就需要加以程式集的名稱。Windows.Forms程式集是公有的程式集,是位於組件快取中的,可以在.net Framwork內部實現side by side執行。所以這個程式集有不同的版本,為了確定使用的版本,我們不僅要提供者集的名稱,還要提供者集的版本和強式名稱。按照這個思路,在我使用的.net Framework 1.1上,將這一句寫成Type.GetType("System.Windows.Forms.CheckBox, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")。現在運行就沒有問題了。問題是我們如何取得所用Windows.Forms程式集的版本和強式名稱?可以用GetType(CheckBox).AssemblyQualifiedName這樣的文法,一旦得到了這些資訊,我們就可以將這些資訊用於其它任何控制項,因為他們都來自於同一個版本Windows.Forms程式集。現在可以來玩一個好玩的,放一個文字框到視窗上,比如叫做TextBox1,將按鈕的事件程序改為:
Try
Dim c As Control = Me.CreateNewControls1(Me.Controls, "Control1", Type.GetType("System.Windows.Forms." & TextBox1.Text & ", System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"), New Size(168, 40), New Point(64, 176))
c.Text = "New Creation"
Catch ex As Exception
MsgBox(ex.Message)
End Try
現在只要在TextBox1種輸入“Button”,按下按鈕,一個新按鈕產生了!如果輸入的是CheckBox,那麼將產生一個複選框。現在無論使用者怎樣刁難,控制項都能正確“按需建立”了。反射機制在.net中還有很多用途,據說Delphi.net中的類引用及虛擬建構函式等功能用於.net Framwork時就是藉助於反射及System.Type類型實現的,善用這一利器會給你的程式增色不少。