程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> emit的基本操作

emit的基本操作

編輯:關於.NET

最近收到《.NET 安全揭秘》的讀者的郵件,提到了書中很多大家想看到的內 容卻被弱化了,我本想回復很多內容因為書的主旨或者章節規劃的原因只是概說 性的,但是轉念一想,讀者需要的,不正是作者該寫的嗎?因此我准備把郵件中 的問題一一搬到博客中,以博文的形式分享給大家。

今天要談論的主題是Emit,反射的孿生兄弟。想要通過幾篇博客詳盡的講解 Emit也是很困難的事情,本系列計劃通過完成一個簡單的Mock接口的功能來講解 ,計劃寫三篇博客:

2)        說說Emit (中)ILGenerator;

<p CxSpLast" style=";margin-left: 45pt">3)        說說Emit (下)Emit在AOP和單元測試中的應用;

這幾篇博客不可能涵蓋Emit所有內容,只希望能讓您知道Emit是什麼,有哪些 基本功能,如何去使用。

1.1 動態實現接口的技術需求

第一個需要動態實現接口的需求,是我在開發中遇到的,具體的業務場景會在 《說說Emit (下) Emit在AOP和單元測試中的應用》中細說,先簡要描述代碼級別 要實現的內容。首先我們有類似圖1所示的以Before和After結尾的成對出現的方 法若干。

圖1 若干成對方法

我們根據一定的規則對上圖所示的方法進行分類(分類的規則暫且不提),在 實際調用過程中,不會直接調用上面的方法,而是調用一個名為 IAssessmentAopAdviceProvider的接口的實例,該接口定義如下:

publicinterfaceIAssessmentAopAdviceProvider

 {

     object Before(object value);

     object After(object beforeResult, object value);

   }

負責創建該接口的工廠類定義如下:

staticclassAdviceProviderFactory

 {

    internalstaticIAssessmentAopAdviceProvider GetProvider(AdviceType adviceType, string instanceName,string funcName,MvcAdviceType mvcAdviceType)

     {

        //創建接口的實例

     }

 }

該工廠的職責是根據傳入的參數,選擇類似圖1中的合適的成對方法動態創建 一個IAssessmentAopAdviceProvider接口的實例,然後返回供調用方使用。當然 如果不使用Emit也能實現這樣的需求,這裡我們只討論使用Emit如何實現。

第一個需求簡單介紹到這裡,我們看第二個需求。現在我要在單元測試中測試 某個依賴IAssessmentAopAdviceProvider的類,我們控制 IAssessmentAopAdviceProvider的行為該怎麼辦呢?如果你做過單元測試,一定 會想到Mock,我們可以使用Moq:

Mock<IAssessmentAopAdviceProvider> assessmentAopAdviceProviderMocked = newMock<IAssessmentAopAdviceProvider>();

assessmentAopAdviceProviderMocked.Setup(t => t. Before (It.IsAny<object>())).Returns(expectObject);

現在我也想實現這樣的功能,該怎麼做呢?您先不要驚訝,實現完整的Mock功 能要實現一整套動態代理的框架,我還沒這個雄心壯志,這裡為了演示Emit,我 以最簡單的方式實現對IAssessmentAopAdviceProvider接口的Before方法的Mock ,而且只針對某個特例,只保證這個特例能被調用即可。感興趣的讀者可以去讀 一讀Moq的源碼。

OK,技術需求到此結束,下面我們開始動手吧!

1.2 動態創建完整的程序集

終於進入正題了,對於第一個需求,我們要做的工作描述起來很簡單,創建一 個類,實現IAssessmentAopAdviceProvider接口,期望結果如下:

publicclassAssessmentAopMvcAdviceProvider : IAssessmentAopAdviceProvider

 {

     publicobject Before(object value = null)

     {

      MvcAdviceReportProvider.DeleteUserResultBefore(value);

     }

     publicobject After(object beforeResult, object value = null)

     {

      MvcAdviceReportProvider.DeleteUserResultAfter (beforeResult ,value);

    }

}

上面代碼中方法體內部的調用,工廠類會根據規則動態變更,這裡我們先只考 慮這個特例情況。

首先必要創建類AssessmentAopMvcAdviceProvider,想要創建類型,必要先有 模塊,想要有模塊必須先有程序集,所以我們要先創建程序集。

(注:下面的創建過程和說明改編自《.NET 安全揭秘》第二章)

先看代碼清單2-1。

代碼清單2-1 創建程序集

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Reflection.Emit;

using System.Reflection;

namespace EmitTest

{

 classProgram

 {

     staticvoid Main(string[] args)

     {

         AssemblyName assemblyName = newAssemblyName("EmitTest.MvcAdviceProvider");

      AssemblyBuilder assemblyBuilder= AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

     }

 }

}

AppDomain.CurrentDomain.DefineDynamicAssembly方法返回一個 AssemblyBuilder實例。其中,第一個參數是AssemblyName實例,是程序集的唯一 標識;第二個參數AssemblyBuilderAccess.Run表明該程序集只能用來執行代碼, 不能被持久保存。AssemblyBuilderAccess還有如下選項:

q  AssemblyBuilderAccess.ReflectionOnly:程序集只能在反射上下文 中執行。

q  AssemblyBuilderAccess.RunAndCollect:程序集可以運行和垃圾回 收。

q  AssemblyBuilderAccess.RunAndSave:程序集可以執行代碼而且被持 久保存。

q  AssemblyBuilderAccess.Save:程序集是持久化的,保存之前不可以 執行代碼。

創建了程序集之後,我們繼續向程序集中添加模塊。

注:“程序集是.NET應用程序的基本單位,是CLR運行托管程序的最基本 單位。它通常的表現形式是PE文件,區分PE文件是不是程序集或者說模塊和程序 集的根本區別是程序集清單,一個PE文件如果包含了程序集清單那麼它就是程序 集。”----《.NET 安全揭秘》第二章

我們使用如代碼清單2-2的方式向程序集中添加模塊。

代碼清單 2-2

namespace EmitTest

{

 classProgram

 {

     staticvoid Main(string[] args)

     {

         AssemblyName assemblyName = newAssemblyName("EmitTest.MvcAdviceProvider");

      AssemblyBuilder assemblyBuilder= AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

      ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProvider");

           }

 }

}

在代碼清單2-2中,我們使用AssemblyBuilder.DefineDynamicModule 方法來 創建模塊,該方法共有三個重載,如下表所示:

模塊定義完成之後,到了略微關鍵的一步,定義類型。我們要定義的類型必須 繼承並實現IAssessmentAopAdviceProvider接口。實現代碼如清單2-3。

代碼清單2-3

namespace EmitTest

{

 classProgram

 {

     staticvoid Main(string[] args)

     {

         AssemblyName assemblyName = newAssemblyName("EmitTest.MvcAdviceProvider");

      AssemblyBuilder assemblyBuilder= AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

      ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProvider");

      TypeBuilder typeBuilder = moduleBuilder.DefineType("MvcAdviceProvider", TypeAttributes.Public,

                                                         typeof(object), newType[] { typeof(IAssessmentAopAdviceProvider) });

   

     }

 }

}

上述代碼中mb.DefineType方法返回一個TypeBuilder實例,該方法有6個重載 方法,這裡采用的方法有四個參數,第一個參數是類型名稱,第二個參數的 TypeAttributes枚舉是類型的訪問級別和類型類別等其他信息,第三個參數是類 型繼承的基類,第四個參數是類型實現的接口。其他重載函數的說明如下(引自 MSDN):

通過TypeBuilder,可以使用TypeBuilder.DefineField來定義字段,使用 TypeBuilder.DefineConstructor來定義構造函數,使用 TypeBuilder.DefineMethod來定義方法,並使用TypeBuilder.DefineEvent來定義 事件等,總之可以定義類型裡的任何成員。這裡我們只需要定義方法,如代碼清 單2-4所示。

namespace EmitTest

{

 classProgram

 {

     staticvoid Main(string[] args)

     {

         AssemblyName assemblyName = newAssemblyName("EmitTest.MvcAdviceProvider");

         AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

         ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProvider");

         TypeBuilder typeBuilder = moduleBuilder.DefineType("MvcAdviceProvider", TypeAttributes.Public,

                                                            typeof(object), newType[] { typeof(IAssessmentAopAdviceProvider) });

         MethodBuilder methodBuilder = typeBuilder.DefineMethod("Before", MethodAttributes.Public, typeof(object), newType[] { typeof(object)});

     }

 }

}

在上面的代碼中,使用TypeBuilder.DefineMethod 方法來創建MethodBuilder 對象。該方法有5個重載,如下表(引自MSDN):

如果需要定義構造函數,可以使用DefineConstructor和 DefineDefaultConstructor方法。

在定義了方法之後,還可以使用MethodBuilder.SetSignature方法設置參數的 數目和類型。MethodBuilder.SetParameters方法會重寫 TypeBuilder.DefineMethod 方法中設置的參數信息。當我們的方法接收泛型參數 的時候,需要使用MethodBuilder.SetParameters方法來設定泛型參數。

定要了方法,還沒有方法體,方法體需要使用ILGenerator類向其中注入il代 碼。ILGenerator的使用,我們單獨放在下一篇博客中,Emit的方法調用的內容會 放在第三篇博客中。

現在我們在Main方法中,輸出我們剛才創建的程序集的信息,看看創建是否成 功。

 classProgram

 {

     staticvoid Main(string[] args)

     {

         AssemblyName assemblyName = newAssemblyName("EmitTest.MvcAdviceProvider");

         AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

         ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProvider");

         TypeBuilder typeBuilder = moduleBuilder.DefineType("EmitTest.MvcAdviceProvider", TypeAttributes.Public,

                                                            typeof(object), newType[] { typeof(IAssessmentAopAdviceProvider) });

       

         MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod("Before", MethodAttributes.Public, typeof(object), newType[] { typeof(object)});

         MethodBuilder afterMethodBuilder = typeBuilder.DefineMethod("After", MethodAttributes.Public, typeof(object), newType[] { typeof(object), typeof(object) });

         TestType(typeBuilder);

     }

     privatestaticvoid TestType(TypeBuilder typeBuilder)

     {

         Console.WriteLine (typeBuilder.Assembly.FullName);

         Console.WriteLine (typeBuilder.Module.Name);

         Console.WriteLine (typeBuilder.Namespace);

         Console.WriteLine (typeBuilder.Name);

                 Console.Read();

     }

       }

此時方法只有定義,還沒有方法體,所以還不能創建類型的實例,顯示結果如 下:

(這裡也留給大家一個小問題:為什麼上圖中輸出的模塊名稱是“在內 存模塊中”呢?)

1.3  構建工廠類雛形

還記上面提到的工廠類和要實現的目標代碼吧,因為還沒有描述業務場景,我 們先不著急實現它的完整功能,現在不需要它接收任何參數,返回一個特定的 IAssessmentAopAdviceProvider接口實例即可。雛形代碼如下:

publicstaticclassAdviceProviderFactory

 {

     staticDictionary<string, IAssessmentAopAdviceProvider> instanceDic;

     staticreadonlyAssemblyName assemblyName = newAssemblyName("EmitTest.MvcAdviceProvider");

     staticAssemblyBuilder assemblyBuilder;

     staticModuleBuilder moduleBuilder;

     publicstatic AdviceProviderFactory()

     {

         instanceDic = newDictionary<string, IAssessmentAopAdviceProvider>();

         assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

         moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProvider");

     }

     internalstaticIAssessmentAopAdviceProvider GetProvider()

     {

         //創建接口的實例

       return  CreateInstance ("MvcAdviceReportProvider");

         

     }

     privatestaticIAssessmentAopAdviceProvider CreateInstance(string instanceName)

     {

         if (instanceDic.Keys.Contains (instanceName))

         {

             return instanceDic [instanceName];

         }

         else

         {

             TypeBuilder typeBuilder = moduleBuilder.DefineType ("EmitTest.MvcAdviceProvider", TypeAttributes.Public,

                                                                typeof(object), newType[] { typeof (IAssessmentAopAdviceProvider) });

             MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod("Before", MethodAttributes.Public, typeof(object), newType[] { typeof(object) });

             MethodBuilder afterMethodBuilder = typeBuilder.DefineMethod("After", MethodAttributes.Public, typeof(object), newType[] { typeof(object), typeof(object) });

             //todo:注入iL代 碼,

             Type providerType = typeBuilder.CreateType();

             IAssessmentAopAdviceProvider provider = Activator.CreateInstance (providerType) asIAssessmentAopAdviceProvider;

             instanceDic.Add (instanceName, provider);

             return provider;

         }

     }

 }

查看本欄目

1.4  構建Mock類雛形

上面說到Mock類要實現的效果,我們也為它構建一個殼出來。代碼如下:

publicclassMock<T> where T : IAssessmentAopAdviceProvider

 {

     public T Obj {

         get { return ConfigObj(this); }

         set; }

     publicSetupContext Contex { get; set; }

     public Mock()

     {

         Obj = (T) AdviceProviderFactory.GetProvider();

      }

     private T ConfigObj(Mock<T> mock)

     {

         returndefault(T);//這裡根據 SetupContext重新配置方法

      }

 }

這是一個最簡單的Mock,只能用來演示,甚至沒任何實際應用價值。其中 SetupContext對象用來記錄執行Setup和Return擴展方法時的配置信息,定義如下 :

publicclassSetupContext

 {

     publicstring MethodName { get; set; }

     publicobject ReturnVlaue { get; set; }

 }

此外定義了三個擴展方法,用來配置Mock行為,定義如下:

publicstaticclassMockExtention

 {

     publicstaticMock<T> Setup<T> (thisMock<T> mocker, Expression<Action<T>> expression)

     {

         mocker.Contex = newSetupContext ();

         mocker.Contex.MethodName = expression.ToMethodInfo().Name;

         return mocker;

     }

     publicstaticvoid Returns<T> (thisMock<T> mocker, object returnValue)

     {

         mocker.Contex.ReturnVlaue = returnValue;

       

     }

     publicstaticMethodInfo ToMethodInfo (thisLambdaExpression expression)

     {

         MemberExpression memberExpression = expression.Body asMemberExpression;

         if (memberExpression != null)

         {

             PropertyInfo propertyInfo = memberExpression.Member asPropertyInfo;

             if (propertyInfo != null)

             {

                 return propertyInfo.GetSetMethod(true);

             }

           

         }

         returnnull;

     }

 }

現在基本的殼已經有了,後續的實現也不會考慮的太復雜,只根據配置的方法 名返回對應的返回值,不會考慮參數對結果的影響。這裡把泛型類型約定為 IAssessmentAopAdviceProvider,是為了演示方便,可以很方便的擴展為任意類 型,不過實現起來也就復雜了。Mock調用了AdviceProviderFactory來初始化對象 的默認值,也就是說在默認情況下會走實際的代碼邏輯。現在我們可以按如下方 式使用這段代碼了:

Mock<IAssessmentAopAdviceProvider> mock = newMock<IAssessmentAopAdviceProvider>();

mock.Setup(t => t.Before(null)).Returns(new { a=""});

到目前為止,我們的准備工作已經完成了,仿佛正題還未開始,是不是太啰嗦 了呢?下一篇博客,會專注於ILGenerator,並實現上面的工廠類和Mock類。

者:玄魂

出處:http://www.cnblogs.com/xuanhun/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved