天天看點

[轉載]利用反射動态建立對象

在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控件,寫入以下代碼:

[轉載]利用反射動态建立對象

private void btnLoadForm_Click(object sender, System.EventArgs e)

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

{            

[轉載]利用反射動态建立對象

            Assembly assembly = Assembly.LoadFrom(@"e:\AutoFormFirstForm.dll");

[轉載]利用反射動态建立對象

            Type type = assembly.GetType("FirstForm.Form1");

[轉載]利用反射動态建立對象

            object obj = Activator.CreateInstance(type);

[轉載]利用反射動态建立對象

            Form formToShow = (Form)obj;

[轉載]利用反射動态建立對象

            formToShow.MdiParent = this;

[轉載]利用反射動态建立對象

            formToShow.Show();

[轉載]利用反射動态建立對象

        }

代碼說明:

1)首先是通過Assembly.LoadFrom()來加載dll檔案;

2)再通過GetType()來獲得要建立的Form類對象的類型。注意,在GetType()方法的參數為類型的名字,為string類型,同時該名字應為類型的FullName,即:命名空間名.類名;

3)然後通過Activator.CreateInstance()方法建立該類型對象,傳回object對象。

4)再将該對象強制轉換為Form類型。

5)最後調用即可。

運作程式,單擊按鈕,結果如下:

[轉載]利用反射動态建立對象

結論:可以看到,對于.Net自身提供的類對象,我們對它直接強制轉換即可。不會出現任何問題。

二、自定義對象

前面已經說過,對于自定義的對象,進行強制轉換會抛出異常。是以,我們需要做些變通才行。

我們說,動态加載的Dll和手工添加的dll引用,系統會認為不是同一個Assembly。那麼應該怎麼解決?想一想,對了,應該使用接口。但是,這裡使用接口的方法稍微有點特殊。還是先按步驟來講解吧。

1、建立一個接口,該接口包括要加載對象類的方法、屬性等:

建立一個“類庫”項目,取名為AutoObjectInterface:

[轉載]利用反射動态建立對象

using System;

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

namespace AutoObjectInterface

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

{    

[轉載]利用反射動态建立對象

    public interface IAutoObject

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

{

[轉載]利用反射動态建立對象

        void Print(string s);

[轉載]利用反射動态建立對象

    }        

[轉載]利用反射動态建立對象

}

這個接口很簡單,隻是提供一個Print()方法而已。

然後将它編譯為Dll檔案,名為AutoObjectInterface。

2、建立自定義類對象:

建立一個“類庫”項目,取名為AutoObject,添加前面建立的接口Dll引用:

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

namespace AutoObject

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

    public class TestObject:AutoObjectInterface.IAutoObject

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

{        

[轉載]利用反射動态建立對象

        public TestObject()

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

        public void Print(string s)

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

            Console.WriteLine(s);

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

    }

[轉載]利用反射動态建立對象

這個類實作了第一步建立的接口。注意,這裡實作的接口不是直接寫在該類中,而是獨立的Dll。在這個類中,是添加了該接口的dll,然後再實作它。這就是前面說的使用接口的一點特殊性。為什麼要這樣,是因為後面動态加載時,也要引用該接口Dll。我們動态建立後的對象,正是轉換為該接口對象。由于實際的類和動态建立的類都引用并實作了該接口Dll,是以它的轉換才能成功。這正是實作的關鍵!

也許有人疑問:我們能否将接口就放在要建立的類中,然後實作它。編譯成dll檔案,然後動态加載該dll,同時也手動添加該dll。動态建立後的對象,再強制轉換為這個接口類型,不可以嗎?答案當然是否定的,為什麼?别問我,我也不知道!總之,我現在講的方法,才是通過反射動态建立自定義對象的不二法門!!

也許會有人說我太武斷!如果你不信,去試試。如果用另外的方法能成功,我一定改正錯誤。至少現在我能這樣武斷。

言歸正傳。現在我們再将給類編譯為dll。名為AutoObject.dll,放到e:\NewObject中。

3、利用反射動态建立該對象:

建立一個控制台項目,取名為StudyReflection,添加前面建立的接口Dll引用。代碼如下:

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

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");

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

            AutoObjectInterface.IAutoObject iObj = (AutoObjectInterface.IAutoObject)obj;

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

            iObj.Print("wayfarer");

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

            Console.ReadLine();

[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象
[轉載]利用反射動态建立對象

說明:這個代碼和前面建立.Net自身提供的對象差不多,關鍵的差別就是強制轉換。因為是自定義對象,是以我們不知道轉換為什麼對象啊,是以要添加接口的引用。轉換的時候就轉換為接口的類型:AutoObjectInterface.IAutoObject iObj = (AutoObjectInterface.IAutoObject)obj;

這樣我們就可以通過接口對象執行個體來調用類對象的方法Print()了。運作後,一切OK。

結論:在通過反射動态建立對象時,一定要注意差別所建立對象的類型。如果是自定義對象,必須通過單獨的接口,來進行類型的轉換。

繼續閱讀