C#筆記_1-動態生成類,程序集

利用反射動態創建對象(如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!!!”



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章