Emit創建常見元素—自定義類型

原文地址:http://www.cnblogs.com/MythYsJh/archive/2010/04/28/1723398.html

本文講述用Emit創建枚舉,結構體,類,接口等類型.

  同樣,讓我們先搭好大體框架:

            AssemblyName assemName = new AssemblyName();
            assemName.Name 
= "EmitStudy3";

            AssemblyBuilder asmBuilder 
=
               AppDomain.CurrentDomain.DefineDynamicAssembly(assemName,
                  AssemblyBuilderAccess.RunAndSave);

            var mdlBldr 
= asmBuilder.DefineDynamicModule("EmitStudy3""EmitStudy3.dll");

            
//TODO:添加自定義類型

            asmBuilder.Save(
"EmitStudy3.dll");

 

一、創建枚舉

  枚舉通過ModuleBuilder.DefineEnum來實現。

       var enumBldr = mdlBldr.DefineEnum("Sex", TypeAttributes.Public, typeof(int));

 

  這裏定義了一個基礎類型爲int,名稱爲Sex的public枚舉。下面,讓我們再爲該枚舉添加'內容':

            enumBldr.DefineLiteral("Male"0);
            enumBldr.DefineLiteral(
"Female"1);

 

  之後也需要像創建類一樣使用

            enumBldr.CreateType();

 

來完成一個枚舉的創建。這是用Reflector查看就可以看到如下一個枚舉了:

public enum Sex
{
    Male,
    Female
}

 

 

 

  二、結構體(struct)

  在ModuleBuilder中並不存在DefineStruct之類的方法。那該如何創建一個結構體呢?我們首先用C#創建一個結構體然後反編譯成IL就會發現結構體實質是繼承於ValueType的一個類。因此我們可以這樣來創建一個結構體:

            var nameStruct = mdlBldr.DefineType("Name", TypeAttributes.Public, typeof(ValueType));
            var fldFirstName 
= nameStruct.DefineField("FirstName"typeof(string), FieldAttributes.Private);
            var fldLastName 
= nameStruct.DefineField("LastName"typeof(string), FieldAttributes.Private);

            var methodGetFirstName 
= nameStruct.DefineMethod("GetFirstName", MethodAttributes.Public, CallingConventions.Standard, typeof(string), null);
            var methodSetFirstName 
= nameStruct.DefineMethod("SetFirstName", MethodAttributes.Public, CallingConventions.Standard, null,new []{ typeof(string)});

            var ilGetFirstName 
= methodGetFirstName.GetILGenerator();
            ilGetFirstName.Emit(OpCodes.Ldarg_0);
            ilGetFirstName.Emit(OpCodes.Ldfld, fldFirstName);
            ilGetFirstName.Emit(OpCodes.Ret);

            var ilSetFirstName 
= methodSetFirstName.GetILGenerator();
            ilSetFirstName.Emit(OpCodes.Ldarg_0);
            ilSetFirstName.Emit(OpCodes.Ldarg_1);
            ilSetFirstName.Emit(OpCodes.Stfld, fldFirstName);
            ilSetFirstName.Emit(OpCodes.Ret);

            var prptFirstName 
= nameStruct.DefineProperty("FirstName", PropertyAttributes.None, typeof(string), null);
            prptFirstName.SetGetMethod(methodGetFirstName);
            prptFirstName.SetSetMethod(methodSetFirstName);

            var methodGetLastName 
= nameStruct.DefineMethod("GetLastName", MethodAttributes.Public, CallingConventions.Standard, typeof(string), null);
            var methodSetLastName 
= nameStruct.DefineMethod("SetLastName", MethodAttributes.Public, CallingConventions.Standard, nullnew[] { typeof(string) });

            var ilGetLastName 
= methodGetLastName.GetILGenerator();
            ilGetLastName.Emit(OpCodes.Ldarg_0);
            ilGetLastName.Emit(OpCodes.Ldfld, fldLastName);
            ilGetLastName.Emit(OpCodes.Ret);

            var ilSetLastName 
= methodSetLastName.GetILGenerator();
            ilSetLastName.Emit(OpCodes.Ldarg_0);
            ilSetLastName.Emit(OpCodes.Ldarg_1);
            ilSetLastName.Emit(OpCodes.Stfld, fldLastName);
            ilSetLastName.Emit(OpCodes.Ret);

            var prptLastName 
= nameStruct.DefineProperty("LastName", PropertyAttributes.None, typeof(string), null);
            prptLastName.SetGetMethod(methodGetLastName);
            prptLastName.SetSetMethod(methodSetLastName);

            nameStruct.CreateType();

 

  上面就創建了一個包含FirstName和LastName的結構體Name。

  

  三、創建接口

  同樣在ModuleBuilder中也沒有DefineInterface這樣的方法。而通過觀察IL代碼同樣可以發現接口其實也是類。因此我們還是可以想創建類一樣來創建接口.

            var iStudentBldr = mdlBldr.DefineType("IStudent", TypeAttributes.Public| TypeAttributes.Interface | TypeAttributes.Abstract, null);
            iStudentBldr.DefineMethod(
"Update", MethodAttributes.Virtual | MethodAttributes.Abstract, nullnull);
            iStudentBldr.CreateType();

 

這樣就創建了一個僅包含一個Update方法的接口IStudent。在創建接口時也要注意TypeAttributes需聲明爲 TypeAttributes.Interface | TypeAttributes.Abstract,而接口包含的方法的MethodAttributes則必須爲MethodAttributes.Virtual | MethodAttributes.Abstract

  

  四、創建類

  已經有很多這方面的例子,這裏就不再贅述。

 

  五、委託和事件

  老規矩,從反編譯的IL代碼入手。可以發現我們創建的委託其實是派生自MultiDelegate類的類。而MultiDelegate這個類比較特殊,它不能從C#代碼裏被繼承,但在IL代碼卻可以。因此可以如下來定義委託。

            var delPropertyChangedHandler = mdlBldr.DefineType("PropertyChanedHandler", TypeAttributes.Public, typeof(MulticastDelegate));
            delPropertyChangedHandler.CreateType();

 

  但是這樣的定義似乎總覺得很奇怪,因爲委託還包含返回值及參數等信息,這些在這段代碼都沒有得到體現。那麼該如何創建委託呢?讓我們還是從現有的入手,以EventHandler爲例,在Reflector下它的結構爲

     

      事實上,創建委託必須包含一個構造函數和Invoke方法。Invoke方法的參數則是委託的參數,返回類型即爲委託的返回類型。

            var delPropertyChangedHandler = mdlBldr.DefineType("PropertyChanedHandler", TypeAttributes.Public | TypeAttributes.Sealed, typeof(MulticastDelegate));
            var constructor 
= delPropertyChangedHandler.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(object), typeof(IntPtr) });
            constructor.SetImplementationFlags(MethodImplAttributes.Runtime 
| MethodImplAttributes.Managed);

            var methodInvoke 
= delPropertyChangedHandler.DefineMethod("Invoke", MethodAttributes.Public | MethodAttributes.Virtual, nullnew[] { typeof(string) });
            methodInvoke.SetImplementationFlags(MethodImplAttributes.Managed 
| MethodImplAttributes.Runtime);

            var delType = delPropertyChangedHandler.CreateType();

 

構造函數和Invoke方法都使用了SetImplementationFlags(MethodImplAttributes.Managed | MethodImplAttributes.Runtime),因爲二者的方法體是在運行時才能決定的。

       既然已經創建了委託,讓我們再用此委託添加一個事件。定義事件就相對容易些了,直接使用TypeBuilder.DefineEvent即可.

          var typeBldr = mdlBldr.DefineType("Sdudent", TypeAttributes.Public);
          
//其餘部分
          typeBldr.DefineEvent("OnNameChanged", EventAttributes.None, delType);
          typeBldr.CreateType();

 

 

六、小結

      我們已經可以用Emit創建常見元素了。當然,Emit還可以創建很多其他內容,不可能介紹完全,關鍵是要掌握通過C#和IL對比來完成Emit的基本方法。這樣,我們就基本可以解決遇到的大部分問題了。歡迎大家指正。

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