摘自:http://hi.baidu.com/yangyuhang/blog/item/f12ea90e13f214e336d12250.html
在VS.Net中,有很多種方法動態調用對象的構造函數。一是通過Activator類的CreateInstance()方法。這個方法我們在Remoting中也用過。它實際上是在本地或從遠程創建對象類型,或獲取對現有遠程對象的引用。它的方法簽名是:public static object CreateInstance(Type);(還有其他重載方法)注意它的返回值為object,MSDN對返回值的描述是:對新創建對象的引用。
二是通過Assembly類的方法CreateInstance()。方法名和前一樣,不過它不是靜態方法。Assembly是在System.Reflection命名空間中。方法簽名:public object CreateInstance(Type);(同樣還有其他重載方法)返回值仍然是object,MSDN對返回值的描述是:表示該類型的 Object 的實例,其區域性、參數、聯編程序和激活屬性設置為空引用(Visual Basic 中為 Nothing),並且 BindingFlags 設置為 Public 或 Instance,或者設置為空引用 (Nothing)(如果沒有找到 typeName)。
當然還有其他方法,例如通過MethodInfo獲得方法信息后,根據IsConstructor屬性,判斷是否構造函數,再根據GetParamters()方法獲得參數,最后通過Invoke()方法來調用,等等……。大家可以參考MSDN。
在這里,我且把問題簡單化,只調用其默認構造函數。通過CreateInstance()方法獲得object對象,再轉換為實際的自定義對象類型。事實證明,這種轉換為出現異常。根本的原因我還弄不清楚,初步的猜測,對於動態加載的assembly,和手動添加的assembly,Framework將兩者視為了不同的assembly,即使我們使用的是同一個DLL。
我也注意到Actovator.CreateInstance()返回的是新創建對象的引用。是否是引用再作怪呢?但Assembly.CreateInstance()方法,根據MSDN的描述,返回的是object實例,然而仍然會拋出同樣的異常。所以,出現問題的具體原因,我確實無法解釋。
確實VS.Net博大精深,很多內在的運行機制我們不得而知。好吧,我們就知其然而不知其所以然吧,至少我現在已經知道了利用反射動態創建對象的解決之道!管它這么多,只要會用就行,退而求其次,也未嘗不可。
利用反射動態創建對象,事實上就是通過Assembly動態加載DLL。這里所謂的“對象”,應分為兩種類型。不同的類型,解決的辦法也不相同。
一、.Net自身提供的類對象,例如Form對象、Control對象。
這也是我們在程序開發中會經常用到的。一般我們開發應用程序,都是將界面定義好。有多少個窗口,有多少個控件,事先做好,放在項目中。但有時作交互設計時,還需要考慮用戶的請求。也許用戶希望某些窗體能夠自己決定加載的時間。也就是說,需要提供運行時加載的功能。這時,就需要通過反射來動態創建對象了。(加載的窗體對象dll,通常是放在配置文件中。在.Net中,有專門的配置文件,它是xml格式。有關配置文件,我希望能專門寫一篇文章。在本文,我的例子是固定的加載程序集。) 1、創建要動態加載的窗體對象
首先,創建一個窗體對象FirstForm,這個窗體只有一個控件Lable,來顯示窗體的名稱。然后我們將它編譯為dll文件FirstForm.dll,放在e:/AutoForm中。(要生成Dll文件,而不是exe,請在Solution Explorer(解決方案資源管理器)中的 FirstForms 項目上單擊鼠標右鍵,選擇 Properties(屬性)。在 Output Type(輸出類型)組合框中選擇 Class Library(類庫)。)
這個對象的程序集名為FirstForm.dll,類型為FirstForm.Form1。
2、創建應用程序,動態加載該對象
啟動一個新的 Windows 窗體項目。將其命名為 AutoLoadForm。在新項目中包含的空窗體 Form1 中,將它的IsMdiContainer 屬性更改為 True。這樣,該窗體即變成一個 MDI 父窗體。更改窗體的大小,使窗體的長和寬的尺寸大約為默認值的兩倍。
將一個面板控件拖動到窗體上,然后設置它的 Dock 屬性,使它靠接在窗體的頂部。更改面板的大小,使它的高度大約為 50px。
將一個組合框拖動到面板上。將它命名為 cboForms,然后將它的 DropDownStyle 設置為 DropDownList。
最后,將一個按鈕拖動到面板上。將它命名為 btnLoadForm,然后將它的 Text 屬性設置為 Load Form。
此時,Form1 應如圖 1 所示。
然后為程序添加命名空間: using System.Reflecton;
單擊btnLoadForm控件,寫入以下代碼:










代碼說明: 1)首先是通過Assembly.LoadFrom()來加載dll文件; 2)再通過GetType()來獲得要創建的Form類對象的類型。注意,在GetType()方法的參數為類型的名字,為string類型,同時該名字應為類型的FullName,即:命名空間名.類名; 3)然后通過Activator.CreateInstance()方法創建該類型對象,返回object對象。 4)再將該對象強制轉換為Form類型。 5)最后調用即可。
運行程序,單擊按鈕,結果如下:
結論:可以看到,對於.Net自身提供的類對象,我們對它直接強制轉換即可。不會出現任何問題。
二、自定義對象
前面已經說過,對於自定義的對象,進行強制轉換會拋出異常。因此,我們需要做些變通才行。
我們說,動態加載的Dll和手工添加的dll引用,系統會認為不是同一個Assembly。那么應該怎么解決?想一想,對了,應該使用接口。但是,這里使用接口的方法稍微有點特殊。還是先按步驟來講解吧。 1、創建一個接口,該接口包括要加載對象類的方法、屬性等:
新建一個“類庫”項目,取名為AutoObjectInterface:












這個接口很簡單,只是提供一個Print()方法而已。
然后將它編譯為Dll文件,名為AutoObjectInterface。
2、創建自定義類對象:
新建一個“類庫”項目,取名為AutoObject,添加前面創建的接口Dll引用:
























這個類實現了第一步創建的接口。注意,這里實現的接口不是直接寫在該類中,而是獨立的Dll。在這個類中,是添加了該接口的dll,然后再實現它。這就是前面說的使用接口的一點特殊性。為什么要這樣,是因為后面動態加載時,也要引用該接口Dll。我們動態創建后的對象,正是轉換為該接口對象。由於實際的類和動態創建的類都引用並實現了該接口Dll,因此它的轉換才能成功。這正是實現的關鍵! 也許有人疑問:我們能否將接口就放在要創建的類中,然后實現它。編譯成dll文件,然后動態加載該dll,同時也手動添加該dll。動態創建后的對象,再強制轉換為這個接口類型,不可以嗎?答案當然是否定的,為什么?別問我,我也不知道!總之,我現在講的方法,才是通過反射動態創建自定義對象的不二法門!!
也許會有人說我太武斷!如果你不信,去試試。如果用另外的方法能成功,我一定改正錯誤。至少現在我能這樣武斷。
言歸正傳。現在我們再將給類編譯為dll。名為AutoObject.dll,放到e:/NewObject中。
3、利用反射動態創建該對象:
新建一個控制台項目,取名為StudyReflection,添加前面創建的接口Dll引用。代碼如下:AutoObjectInterface.IAutoObject
Obj = (AutoObjectInterface.IAutoObject)obj;
using System;
using System.Reflection;
namespace studyReflection
{
class Class1
{
// 應用程序的主入口點。
[STAThread]
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFrom(@"e:NewObjectAutoObject.dll");
Type type = assembly.GetType("AutoObject.TestObject");
object obj = Activator.CreateInstance(type);
AutoObjectInterface.IAutoObject iObj = (AutoObjectInterface.IAutoObject)obj;
iObj.Print("wayfarer");
Console.ReadLine();
}
}
}
說明:這個代碼和前面創建.Net自身提供的對象差不多,關鍵的區別就是強制轉換。因為是自定義對象,所以我們不知道轉換為什么對象啊,所以要添加接口的引用。轉換的時候就轉換為接口的類型:
這樣我們就可以通過接口對象實例來調用類對象的方法Print()了。運行后,一切OK。
結論:在通過反射動態創建對象時,一定要注意區別所創建對象的類型。如果是自定義對象,必須通過單獨的接口,來進行類型的轉換。