利用反射動態創建對象(如string,int,float,bool,以及自定義類等):
“反射”其實就是利用程序集的元數據信息。
反射可以有很多方法,編寫程序時請先導入 System.Reflection 命名空間,假設你要反射一個 DLL 中的類,並且沒有引用它(即未知的類型):
Assembly assembly = Assembly.LoadFile("程序集路徑,不能是相對路徑"); // 加載程序集(EXE 或 DLL)
object obj = assembly.CreateInstance("類的完全限定名(即包括命名空間)"); // 創建類的實例
若要反射當前項目中的類可以爲:
Assembly assembly = Assembly.GetExecutingAssembly(); // 獲取當前程序集
object obj = assembly.CreateInstance("類的完全限定名(即包括命名空間)"); // 創建類的實例,返回爲 object 類型,需要強制類型轉換
也可以爲:
Type type = Type.GetType("類的完全限定名");
object obj = type.Assembly.CreateInstance(type);
反射創建類的實例
上述描述中提到的三種方法其實都是大同小異的,核心就是通過System.Reflection.Assembly 類型的CreateInstance方法創建實例。
關於System.Reflection.Assembly 類可以直接在MSDN上查詢詳細信息http://msdn.microsoft.com/zh-cn/library/system.reflection.assembly(v=vs.110).aspx
那麼簡單的解釋一下這種方法的原理:
1.找到要實例化的類所在的程序集,並將之實例爲System.Reflection.Assembly 類的對象
2.利用System.Reflection.Assembly 類提供的CreateInstance方法,創建類的對象
第一次測試,創建一個簡單的自定義類型對象<pre>class Test
{
private string _strId;
public string ID
{
get { return _strId; }
set { _strId = value; }
}
public Test()
{
}
}
調試結果:顯示obj對象的確不爲空,證明這種方法可行。
第二次測試,加深難度,測試類的構造函數需要傳遞參數
首先修改Test類,將其構造函數改爲:
public Test(string str) { _strId = str; }
調試結果:直接拋出異常:未找到類型“ReflectionTest.Test”上的構造函數。這是因爲CreateInstance方法默認情況下是通過找無參數的構造函數去創建對象的,現在找不到當然會出錯,實時上CreateInstance方法提供了3中籤名,其中有CreateInstance(String, Boolean, BindingFlags, Binder, Object [], CultureInfo, Object []) 就可以滿足這種情況:
修改主函數如下:
Assembly assembly = Assembly.GetExecutingAssembly(); // 獲取當前程序集
//object obj = assembly.CreateInstance("ReflectionTest.Test"); //類的完全限定名(即包括命名空間)
object[] parameters = new object[1];
parameters[0] = "test string";
object obj = assembly.CreateInstance("ReflectionTest.Test",true,System.Reflection.BindingFlags.Default,null,parameters,null,null);// 創建類的實例
調試結果:正常,並且對象中變量值也是正確的,但是這離筆者的需求還差很遠。繼續
第三次測試,繼續加深難度,創建string的對象
首先知道string是System.String的別名,所以要創建的是System.String的對象,而System.String在mscorlib.dll中,所以需要將mscorlib.dll實例爲System.Reflection.Assembly的對象,這裏利用System.Type類型的屬性Assembly來實現功能。
System.String的構造函數有很多種,本文中筆者就不墨跡了,採用String( Char []) 。
最終將主函數中代碼改爲:
Type type = Type.GetType("System.String");
object[] parameters = new object[1];
char[] lpChar = { 't','e','s','t' };
parameters[0] = lpChar;
object obj = type.Assembly.CreateInstance("ReflectionTest.Test",true,System.Reflection.BindingFlags.Default,null,parameters,null,null);// 創建類的實例
調試結果:對象爲空,失敗了,事實上這種方法還有個問題,如將Test類構造函數修改爲
public Test(string str)
{
ID = str;//屬性賦值
}
調試結果:對象創建成功,但是變量爲空
以上問題詳細原因筆者現在也無法解釋,正在查找相關資料。
解決方案
採用System.Activator 類的CreateInstance方法。
最後見代碼:
Type type = Type.GetType("System.String");
object[] parameters = new object[1];
char[] lpCh = { 't', 'e', 's', 't' };
parameters[0] = lpCh;
object obj = Activator.CreateInstance(type, parameters);
調試結果:對象創建成功,且變量值正常
結論
採用System.Activator 類的CreateInstance方法,要比System.Reflection.Assembly的CreateInstance簡單有效很多。
更深一步:
首先需要知道動態創建這些類型是使用的一些什麼技術呢?其實只要相關動態加載程序集呀,類呀,都是使用反射,那麼動態創建也一樣使用的是反射,是屬於反射的技術!也就是將對象或者數據映射成一個對象或者程序集保存起來而已。
首先我們需要了解每個動態類型在.net中都是用什麼類型來表示的。
程序集:System.Reflection.Emit.AssemblyBuilder(定義並表示動態程序集)
構造函數:System.Reflection.Emit.ConstructorBuilder(定義並表示動態類的構造函數)
自定義屬性:System.Reflection.Emit.CustomAttributeBuilder(幫助生成自定義屬性 使用構造函數傳遞的參數來生成類的屬性)
枚舉:System.Reflection.Emit.EnumBuilder(說明並表示枚舉類型)
事件:System.Reflection.Emit.EventBuilder(定義類的事件)
字段:System.Reflection.Emit.FieldBuilder(定義並表示字段。無法繼承此類)
局部變量:System.Reflection.Emit.LocalBuilder(表示方法或構造函數內的局部變量)
方法:System.Reflection.Emit.MethodBuilder(定義並表示動態類的方法(或構造函數))
模塊:System.Reflection.Emit.ModuleBuilder(定義和表示動態程序集中的模塊)
參數:System.Reflection.Emit.ParameterBuilder(創建或關聯參數信息 如:方法參數,事件參數等)
屬性:System.Reflection.Emit.PropertyBuilder(定義類型的屬性 (Property))
類:System.Reflection.Emit.TypeBuilder(在運行時定義並創建類的新實例)
我們有了這些類型,基本上就可以動態創建我們的任何需要使用的類型,當然很多可以動態創建的類型我不可能都介紹完,如果在項目中有需要可以去查閱MSDN,裏面都有DEMO的,主要的問題就是要理解每一種類型的定義,比如:程序集加載是靠AppDomain,程序集裏包含多個模塊,模塊裏可以聲明類,類裏可以創建方法、屬性、字段。方法需要在類中才可以創建的,局部變量是聲明在方法體內等等規則。看MSDN就非常容易弄懂了。
1.如何動態創建它們了
AppDomain:應用程序域(由 AppDomain 對象表示)爲執行託管代碼提供隔離、卸載和安全邊界。AppDomain同時可以載入多個程序集,共同來實現功能。
程序集:簡單來說就是一個以公共語言運行庫(CLR)爲宿主的、版本化的、自描述的二進制文件。(說明:定義來自C#與.NET3.5高級程序設計(第四版))
模塊:類似於以前的單元,用於分割不同的類和類型,以及資源(resource, 資源記錄就是字符串,圖象以及其它數據,他們只在需要的時候纔會被調入內存)。類型的Meta信息也是模塊的一部分。多個模塊組建成一個程序集。
所謂動態就是在程序運行時,動態的創建和使用。
直接看代碼吧,其實超級簡單。
//動態創建程序集
AssemblyName DemoName = new AssemblyName("DynamicAssembly");
AssemblyBuilder dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(DemoName, AssemblyBuilderAccess.RunAndSave);
//動態創建模塊
ModuleBuilder mb = dynamicAssembly.DefineDynamicModule(DemoName.Name, DemoName.Name + ".dll");
//動態創建類MyClass
TypeBuilder tb = mb.DefineType("MyClass", TypeAttributes.Public);
//動態創建字段
FieldBuilder fb = tb.DefineField("", typeof(System.String), FieldAttributes.Private);
//動態創建構造函數
Type[] clorType = new Type[] { typeof(System.String) };
ConstructorBuilder cb1 = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, clorType);
//生成指令
ILGenerator ilg = cb1.GetILGenerator();//生成 Microsoft 中間語言 (MSIL) 指令
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Ldarg_1);
ilg.Emit(OpCodes.Stfld, fb);
ilg.Emit(OpCodes.Ret);
//動態創建屬性
PropertyBuilder pb = tb.DefineProperty("MyProperty", PropertyAttributes.HasDefault, typeof(string), null);
//動態創建方法
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName;
MethodBuilder myMethod = tb.DefineMethod("get_Property", getSetAttr, typeof(string), Type.EmptyTypes);
//生成指令
ILGenerator numberGetIL = myMethod.GetILGenerator();
numberGetIL.Emit(OpCodes.Ldarg_0);
numberGetIL.Emit(OpCodes.Ldfld, fb);
numberGetIL.Emit(OpCodes.Ret);
//保存動態創建的程序集
dynamicAssembly.Save(DemoName.Name + ".dll"); <span style="white-space:pre"> </span>
現在開始動態創建類:
構造函數:System.Reflection.ConstructorInfo(發現類構造函數的屬性並提供對構造函數元數據的訪問權)
事件:System.Reflection.EventInfo(發現事件的屬性並提供對事件元數據的訪問權)
字段:System.Reflection.FieldInfo(發現字段屬性並提供對字段元數據的訪問權)
方法:System.Reflection.MemberInfo(獲取有關成員屬性的信息並提供對成員元數據的訪問)
成員:System.Reflection.MemberInfo(獲取有關成員屬性的信息並提供對成員元數據的訪問)
參數:System.Reflection.ParameterInfo(發現參數屬性並提供對參數元數據的訪問)
屬性:System.Reflection.PropertyInfo (發現屬性 (Property) 的屬性 (Attribute) 並提供對屬性 (Property) 元數據的訪問)
同樣這是一種延伸閱讀,只是先對這些進行了解,如果不知道的話,可能對動態的使用類型就無法下手了。
今天我做了一個Demo,先上Demo吧,然後在來解釋程序是如何執行的。
//動態創建程序集
AssemblyName DemoName = new AssemblyName("DynamicAssembly");
AssemblyBuilder dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(DemoName, AssemblyBuilderAccess.RunAndSave);
//動態創建模塊
ModuleBuilder mb = dynamicAssembly.DefineDynamicModule(DemoName.Name, DemoName.Name + ".dll");
//動態創建類MyClass
TypeBuilder tb = mb.DefineType("MyClass", TypeAttributes.Public);
//動態創建字段
FieldBuilder fb = tb.DefineField("", typeof(System.String), FieldAttributes.Private);
//動態創建構造函數
Type[] clorType = new Type[] { typeof(System.String) };
ConstructorBuilder cb1 = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, clorType);
//生成指令
ILGenerator ilg = cb1.GetILGenerator();//生成 Microsoft 中間語言 (MSIL) 指令
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Ldarg_1);
ilg.Emit(OpCodes.Stfld, fb);
ilg.Emit(OpCodes.Ret);
//動態創建屬性
PropertyBuilder pb = tb.DefineProperty("MyProperty", PropertyAttributes.HasDefault, typeof(string), null);
//動態創建方法
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName;
MethodBuilder myMethod = tb.DefineMethod("get_Property", getSetAttr, typeof(string), Type.EmptyTypes);
//生成指令
ILGenerator numberGetIL = myMethod.GetILGenerator();
numberGetIL.Emit(OpCodes.Ldarg_0);
numberGetIL.Emit(OpCodes.Ldfld, fb);
numberGetIL.Emit(OpCodes.Ret);
//保存動態創建的程序集
dynamicAssembly.Save(DemoName.Name + ".dll");
執行的主要方法
//動態創建程序集
AssemblyName DemoName = new AssemblyName("DynamicAssembly");
AssemblyBuilder dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(DemoName, AssemblyBuilderAccess.RunAndSave);
//動態創建模塊
ModuleBuilder mb = dynamicAssembly.DefineDynamicModule(DemoName.Name, DemoName.Name + ".dll");
//動態創建類MyClass
TypeBuilder tb = mb.DefineType("MyClass", TypeAttributes.Public);
//動態創建字段
FieldBuilder fb = tb.DefineField("", typeof(System.String), FieldAttributes.Private);
//動態創建構造函數
Type[] clorType = new Type[] { typeof(System.String) };
ConstructorBuilder cb1 = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, clorType);
//生成指令
ILGenerator ilg = cb1.GetILGenerator();//生成 Microsoft 中間語言 (MSIL) 指令
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Ldarg_1);
ilg.Emit(OpCodes.Stfld, fb);
ilg.Emit(OpCodes.Ret);
//動態創建屬性
PropertyBuilder pb = tb.DefineProperty("MyProperty", PropertyAttributes.HasDefault, typeof(string), null);
//動態創建方法
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName;
MethodBuilder myMethod = tb.DefineMethod("get_Property", getSetAttr, typeof(string), Type.EmptyTypes);
//生成指令
ILGenerator numberGetIL = myMethod.GetILGenerator();
numberGetIL.Emit(OpCodes.Ldarg_0);
numberGetIL.Emit(OpCodes.Ldfld, fb);
numberGetIL.Emit(OpCodes.Ret);
//保存動態創建的程序集
dynamicAssembly.Save(DemoName.Name + ".dll");
我創建了一個類,類裏創建了有一個字段、有一個參數的構造函數、一個屬性、有一個參數的構造函數和一個方法。用有參數的構造函數來初始化字段myField,然後調用get_Field方法返回myField字段的值。控制檯程序顯示“Hello World!!!”