說說emit(中)ILGenerator


說說emit()ILGenerator

/玄魂

在上一篇博客(說說emit()基本操作)中,我描述了基本的技術實現上的需求,難度和目標范圍都很小,搭建了基本的架子。在代碼中實現了程序集、模塊、類型和方法的創建,唯一的缺憾是方法體。

方法體是方法內部的邏輯,我們需要將這個邏輯用IL代碼描述出來,然后注入到方法體內部。這里自然地引出兩個主題,IL代碼和用來將Il代碼注入到方法體內的工具(ILGenerator)。本篇博客將主要圍繞這兩個主題展開。但是這一篇博客不可能將IL講的很詳細,只能圍繞ILGenerator的應用來講解。若想了解IL的全貌,我想還是要看ECMA的文檔了(http://www.ecma-international.org/publications/standards/Ecma-335.htm)。

2.1 CIL指令簡介

這里我們通過幾個簡單例子來對IL指令有個初步的認識。

新建一個名為“HelloWorld”的控制台項目,代碼如清單2-1(雖然在我之前的文章里用過HelloWorld來解釋Il,雖然無數篇博客都用過這個例子,但是我還是不厭其煩的用它)。

代碼清單2-1  HelloWorld

using System;

 

namespace HelloWorld

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("Hello World");

        }

    }

}

編譯上面的代碼,然后使用ILDasm打開HelloWorld.exe,導出.il文件,內容如下:

//  Microsoft (R) .NET Framework IL Disassembler.  Version 4.0.30319.1

//  Copyright (c) Microsoft Corporation.  All rights reserved.

// Metadata version: v4.0.30319

.assembly extern mscorlib

{

  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..

  .ver 4:0:0:0

}

.assembly HelloWorld

{

 //()

}

.module HelloWorld.exe

// MVID: {CBB65270-D266-4B29-BAC1-4F255546CDA6}

.imagebase 0x00400000

.file alignment 0x00000200

.stackreserve 0x00100000

.subsystem 0x0003       // WINDOWS_CUI

.corflags 0x00020003    //  ILONLY 32BITREQUIRED

// Image base: 0x049F0000

 

 

// =============== CLASS MEMBERS DECLARATION ===================

 

.class private auto ansi beforefieldinit HelloWorld.Program

       extends [mscorlib]System.Object

{

  .method private hidebysig static void  Main(string[] args) cil managed

  {

    .entrypoint

    // Code size       13 (0xd)

    .maxstack  8

    IL_0000:  nop

    IL_0001:  ldstr      "Hello World"

    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)

    IL_000b:  nop

    IL_000c:  ret

  } // end of method Program::Main

 

  .method public hidebysig specialname rtspecialname

          instance void  .ctor() cil managed

  {

    // Code size       7 (0x7)

    .maxstack  8

    IL_0000:  ldarg.0

    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()

    IL_0006:  ret

  } // end of method Program::.ctor

 

} // end of class HelloWorld.Program

在上面的代碼中,隱藏的內容為AssemblyInfo.cs中內容,也就是程序集級別的配置內容。首先注意以”.”開頭的字段,.assembly.module.class.method等等,我們稱之為CIL指令(CIL Directive)。和指令一同使用的,通常直接跟在指令后面的,稱之為CIL 特性(CIL Attributes),上面代碼中的 externextendsprivatepublic都屬於CIL特性,它們的作用是用來描述CIL指令如何被執行。下面先從CIL指令(CIL Directive)的角度看看上面的代碼都告訴了我們什么信息。

.assembly extern mscorlib

{

  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                        

  .ver 4:0:0:0

}

當前程序集引用了程序集mscorlib,該程序集的強名稱簽名公鑰標識為“B7 7A 5C 56 19 34 E0 89”,版本為“4:0:0:0

.assembly HelloWorld

{

 //()

}

定義當前程序集,名稱為HelloWorld

.module HelloWorld.exe

模塊為.module HelloWorld.exe

.imagebase 0x00400000

映像文件基址。

.file alignment 0x00000200

文件對齊大小。

.subsystem 0x0003       // WINDOWS_CUI

指定程序要求的應用程序環境。

.stackreserve 0x00100000

調用堆棧(Call Stack)內存大小。

.corflags 0x00020003    //  ILONLY 32BITREQUIRED

保留字段,未使用。

.class private auto ansi beforefieldinit HelloWorld.Program

       extends [mscorlib]System.Object

聲明類HelloWorld.Programprivate是訪問類型,auto指明內存布局類型,auto表示內存布局由.NET自動決定(LayoutKind,共有三個值:SequentialAutoExplicit),ansi表示在托管和非托管轉換時使用的編碼類型。extends表示繼承。

.method private hidebysig static void  Main(string[] args) cil managed

.method,聲明方法;private,訪問類型;hidebysig,相當於c#方法修飾符newstatic,靜態方法;void ,返回類型;cil managed,表示托管執行。

.entrypoint

程序入口點。

.maxstack  8

執行方法時的計算堆棧大小。

在方法內部,執行邏輯的編碼,被稱作操作碼(OpcodeOperation Code),如nopldstr。操作碼也通常被翻譯為指令,但是它的英文是Instruction而不是Directive,本文稱之為操作指令。完整的操作碼速查手冊,可參考http://wenku.baidu.com/view/143ab58a6529647d27285234.html

操作碼實際上都是二進制指令,每個指令有其對應的命名,比如操作碼0x72對應的名稱為ldstr。在操作碼前面類似“IL_0000:”這些以冒號結尾的單元是(標簽)Label,其值可以任意指定,在執行跳轉時會用到Label

在操作碼之前,都會先設置計算堆棧大小。計算堆棧(Evaluation Stack)是用來保存局部變量和方法傳人參數的空間。在方法執行前后都要保證計算堆棧為空。

從內存中拷貝數據到計算堆棧的操作稱之為Load,以ld開頭的操作指令執行的都是load操作,例如ldc.i4為加載一個32位整型數到計算堆棧中,Ldargs.3為將索引為3的參數加載到計算堆棧上。

從計算堆棧拷貝數據回內存的操作為Store,以st開頭的操作指令執行的操作都是Store,例如stloc.0為從計算堆棧的頂部彈出當前值並將其存儲到索引 0 處的局部變量列表中,starg.s為將位於計算堆棧頂部的值存儲在參數槽中的指定索引處。

在方法體的開始部分,需要指定在方法執行過程中需要的計算堆棧的最大值,也就是.maxstack指令(directive)。在上面的示例程序中,我們指定最大堆棧值為8,事實上它是編譯器指定的默認值。計算運算堆棧的大小最簡單的方法是計算方法參數和變量的個數,但是個數往往大於實際需要的堆棧大小。編譯器往往會對代碼做編譯優化,使指定的堆棧大小更合理(最大使用大小)。例如下面的代碼

   staticvoid Main(string[] args)

        {

            int v1 = 0;

            int v2 = 0;

            int v3 = 0;

            int v4 = 0;

            int v5 = 0;

            int v6 = 0;

            int v7 = 0;

            int v8 = 0;

            int v9 = 0;

            int v10 = 0;

            Console.WriteLine("Hello World");

        }

 

 

編譯之后,編譯器設置的計算堆棧為大小為1

修改成下面的代碼之后,計算堆棧的大小是多少呢?

  classProgram

    {

        staticvoid Main(string[] args)

        {

            int v1 = 0;

            int v2 = 0;

            int v3 = 0;

            int v4 = 0;

            int v5 = 0;

            int v6 = 0;

            int v7 = 0;

            int v8 = 0;

            int v9 = 0;

            int v10 = 0;

            UseParams(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10);

            Console.WriteLine("Hello World");

        }

 

        privatestaticvoid UseParams(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10)

        {

            int sum = v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10;

        }

 

    }

初步統計Main方法的計算堆棧的大小應該是11(變量個數),但是最大使用量是10,所以最終最大計算堆棧的大小應該是10

 

 

 

其實使用計算堆棧的原則很簡單,在使用變量之前將其壓棧,使用后彈棧

這里再啰嗦一句,個人認為學習Il編碼的最簡單方法是先了解基本原理,准備一份指令表,用C#編寫實例代碼,然后使用反編譯工具反編譯查看Il指令,最后再自己模仿編寫。

現在我們回頭看最簡單的HelloWorld程序的內部IL實現。

   .entrypoint

    // Code size       13 (0xd)

    .maxstack  8

    IL_0000:  nop

    IL_0001:  ldstr      "Hello World"

    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)

    IL_000b:  nop

    IL_000c:  ret

逐句解釋下。

IL_0000:  nop

不執行任何push或者pop操作

ldstr      "Hello World"

加載字符串"Hello World"的引用到計算堆棧。

call       void [mscorlib]System.Console::WriteLine(string)

調用程序集為mscorlib中的System.Console類的方法WriteLine。此時會自動彈出計算堆棧中的值賦值為調用方法的參數。

IL_000c:  ret

ret就是return,結束當前方法,返回返回值。

下面我們再來看兩個小例子,加深下理解。

  staticvoid Main(string[] args)

        {

            int v1 = 2;

            object v2 = v1;

            Console.WriteLine((int)v2);

        }

這段代碼,涉及一個簡單的賦值操作和一個裝箱拆箱。我們看對應的IL代碼:

.method private hidebysig static 
    
void Main (
        
string[] args
    ) 
cil managed 
{
    
// Method begins at RVA 0x2050
    
// Code size 23 (0x17)
    
.maxstack 1
    
.entrypoint
    
.locals init (
        [0] 
int32 v1,
        [1] 
object v2
    )

    IL_0000: 
nop
    IL_0001: 
ldc.i4.2
    IL_0002: 
stloc.0
    IL_0003: 
ldloc.0
    IL_0004: 
box [mscorlib]System.Int32
    IL_0009: 
stloc.1
    IL_000a: 
ldloc.1
    IL_000b: 
unbox.any [mscorlib]System.Int32
    IL_0010: 
call void [mscorlib]System.Console::WriteLine(int32)
    IL_0015: 
nop
    IL_0016: 
ret
// end of method Program::Main

  首先是局部變量的聲明,IL會在每方法的頂部聲明所有的局部變量,使用.locals init

 .locals init ( [0] int32 v1,[1] object v2 )

在示例中聲明了v1v2兩個局部變量。事實上這里不僅僅是聲明這么簡單,這里必須要開辟內存空間, 若要開辟內存空間必須要賦值,也就是說聲明的同時要進行賦值,這就是默認值的由來。這個操作就是指令中的init 完成的。更深入的分析,請參考http://blog.liranchen.com/2010/07/behind-locals-init-flag.html

第一個賦值操作int v1 = 2;是如何完成的呢?

1)        ldc.i4.2,加載32位整型數2到計算堆棧;

2)        stloc.0,從計算堆棧頂部彈出值賦值到局部變量列表中的第一個變量。

再看第二條語句object v2 = v1的實現過程

1)        ldloc.0,加載局部變量列表中的一個變量到計算堆棧中;

2)        box [mscorlib]System.Int32,對計算堆棧中的頂部值執行裝箱操作

3)        stloc.1,從計算堆棧頂部彈出值賦值給局部變量列表中的第二個變量

其他操作類似,就不做解釋了。

我們再看一個while循環的操作,了解循環是如何實現的,c#代碼如下:

  staticvoid Main(string[] args)

        {

            int i = 0;

            while (i < 5)

            {

                i++;

            }

        }

對應的Il代碼為:

.method private hidebysig static 
        
void Main (
            
string[] args
        ) 
cil managed 
    {
        
// Method begins at RVA 0x2050
        
// Code size 20 (0x14)
        
.maxstack 2
        
.entrypoint
        
.locals init (
            [0] 
int32 i,
            [1] 
bool CS$4$0000
        )

        IL_0000: 
nop
        IL_0001: 
ldc.i4.0
        IL_0002: 
stloc.0
        IL_0003: 
br.s IL_000b
        
// loop start (head: IL_000b)
            IL_0005: 
nop
            IL_0006: 
ldloc.0
            IL_0007: 
ldc.i4.1
            IL_0008: 
add
            IL_0009: 
stloc.0
            IL_000a: 
nop

            IL_000b: 
ldloc.0
            IL_000c: 
ldc.i4.5
            IL_000d: 
clt
            IL_000f: 
stloc.1
            IL_0010: 
ldloc.1
            IL_0011: 
brtrue.s IL_0005
        
// end loop

        IL_0013: 
ret
    } 
// end of method Program::Main

首先聲明了一個bool類型的局部變量([1] bool CS$4$0000)。在循環開始之前,先執行 

  IL_0003: br.s IL_000b

跳轉到IL_000b處。先加載變量到計算堆棧:

   IL_000b: ldloc.0
            IL_000c: ldc.i4.5

然執行比較操作:

            IL_000d: clt

如果第一個值小於第二值,將整數1壓入計算堆棧,否則將整數0壓入計算堆棧,同時執行彈棧操作。接下來將比較結果賦值給局部變量列表中的第二個變量:

  IL_000f: stloc.1

之后再將局部變量列表中的第二個變量壓棧:

 IL_0010: ldloc.1

然后判斷bool值,確定是否執行循環體內代碼:

  IL_0011: brtrue.s IL_0005

如果為true,跳轉到 IL_0005處,然后繼續執行加法操作:

   IL_0005: nop
            IL_0006: 
ldloc.0
            IL_0007: 
ldc.i4.1
            IL_0008: 
add
            IL_0009: 
stloc.0
            IL_000a: 
nop

  IL_000a: nop執行之后再從 IL_000b處開始新一輪的循環。

關於IL指令的介紹就到這里,不然就收不住筆了,越寫越多。現在把思路收回到Emit,當我們了解相關C#代碼如何用Il實現之后,下一步就是考慮將Il之類植入方法內部,在Emit的過程中,我們拿什么來表達要植入的Il指令呢?是原生的字符串嗎?當然不是,.NET准備了OpCodes 類,該類以字段的形式封裝了操作碼。

 

 

 

解決了寫操作碼的問題,下一個問題就是如何發出(Emit)操作碼到方法內部了?這就是ILGenerator類。

2.2  ILGenerator

ILGenerator類的功能,一句話,生成IL代碼。想要獲取ILGenerator類的實例,只有三個途徑:

1)        ConstructorBuilder.GetILGenerator方法

2)        DynamicMethod.GetILGenerator 方法

3)        MethodBuilder.GetILGenerator 方法

上面涉及到了在Emit中能夠動態生成方法的三種途徑,ConstructorBuilder類用來配置的構造函數,構造函數內部的IL要使用它的GetILGenerator方法返回的ILGenerator類發出。DynamicMethod類,是在當前運行上下文環境中動態生成方法的類,使用該類不必事先創建程序集、模塊和類型,同樣發出其內部的IL使用DynamicMethod.GetILGenerator方法返回的ILGenerator類實例。MethodBuilder我在《說說emit()基本操作中做了介紹,寫到這里,突然發現很悲劇的是,竟然沒有辦法很順暢的和上篇博客很順暢的銜接起來。看來寫文章也是要講求設計的。既然無法很好的銜接,也就不強求了,這里將上篇博客提到的示例糅合到一起,實現幾個超級簡單的Mock接口的例子。

我要實現的調用效果是這樣的:

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

  mocker.Setup(t => t.Before(3)).Returns("HelloWorld!");

  Console.WriteLine(mocker.Obj.Before(2));

接收一個接口,初始化一個Mock類的實例,然后通過SetupReturns擴展方法設定實現該接口的實例在指定方法上的返回值。這里我們先不考慮對不同參數的處理邏輯。

Mock類的定義如下:

  publicclassMock<T>

    {

        public T Obj

        {

            get;

            set;

        }

        publicSetupContext Contex { get; set; }

        public Mock()

        {

        }

    }

Mock類的Obj屬性是特定接口的實例。Contex屬性是上下文信息,當前內容很簡單,只包含一個MethodInfo屬性。定義如下:

  publicclassSetupContext

    {

        publicMethodInfo MethodInfo { get; set; }

}

這個上下文信息目前只滿足接口有一個方法的情況,對應的相關實現也只考慮一個方法,在這個示例程序中我們無需過分糾結其他細節,以免亂了主次。

接下來是三個擴展方法。

  publicstaticclassMockExtention

    {

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

        {

            mocker.Contex = newSetupContext();

            mocker.Contex.MethodInfo = expression.ToMethodInfo();

            return mocker;

        }

 

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

        {

            if (mocker.Contex != null && mocker.Contex.MethodInfo != null)

            {

                //這里為簡單起見,只考慮IAssessmentAopAdviceProvider接口

             mocker.Obj=  (T)AdviceProviderFactory.GetProvider(mocker.Contex.MethodInfo.Name,(string)returnValue);

            }

          

        }

 

        publicstaticMethodInfo ToMethodInfo(thisLambdaExpression expression)

        {

            var memberExpression = expression.Body as System.Linq.Expressions.MethodCallExpression;

;

            if (memberExpression != null)

            {

                return memberExpression.Method;

            }

            returnnull;

        }

    }

SetupMock類的擴展方法,配置要Mock的方法信息;Returns擴展方法則調取對應的工廠獲取接口的實例。

ToMethodInfoLambdaExpression擴展方法,該方法從Lambda表達式中獲取MethodInfo

這里對應的對象工廠也簡單化,直接返回IAssessmentAopAdviceProvider接口實例。

 

 

 

首先,在構造函數中,初始化assemblyBuildermoduleBuilder,代碼如下:

    static AdviceProviderFactory()

        {

                       assemblyName.Version = newVersion("1.0.0.0");

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

            moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProviderModule", "test.dll",true);

 

        }

上面的代碼就不解釋了,相關內容在前一篇博客有詳細的解釋。

GetProvider方法當前沒有任何邏輯,只是調用了CreateInstance方法。

代碼如下:

publicstaticIAssessmentAopAdviceProvider GetProvider(string methodName,string returnValue)

        {

            //創建接口的實例

          return  CreateInstance("MvcAdviceReportProviderInstance",methodName,returnValue);

           

        }

CreateInstance方法負責創建類型和方法的實現:

privatestaticIAssessmentAopAdviceProvider CreateInstance(string instanceName,string methodName,string returnValue)

        {

 

            TypeBuilder typeBuilder = moduleBuilder.DefineType("MvcAdviceProvider.MvcAdviceProviderType", TypeAttributes.Public, typeof(object), newType[] { typeof(IAssessmentAopAdviceProvider) });

           // typeBuilder.AddInterfaceImplementation(typeof(IAssessmentAopAdviceProvider));

            MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Virtual, typeof(string), newType[] { typeof(int) });

                beforeMethodBuilder.DefineParameter(1, ParameterAttributes.None ,"value");

           

                ILGenerator generator1 = beforeMethodBuilder.GetILGenerator();

              

              LocalBuilder local1=  generator1.DeclareLocal(typeof(string));

              local1.SetLocalSymInfo("param1");

                generator1.Emit(OpCodes.Nop);

                generator1.Emit(OpCodes.Ldstr, returnValue);

                generator1.Emit(OpCodes.Stloc_0);

                generator1.Emit(OpCodes.Ldloc_0);

                generator1.Emit(OpCodes.Ret);

              

 

                Type providerType = typeBuilder.CreateType();

                assemblyBuilder.Save("test.dll");

              IAssessmentAopAdviceProvider provider = Activator.CreateInstance(providerType) asIAssessmentAopAdviceProvider;

 

              return provider;

 

           

        }

CreateInstance方法中,首先定義類型:

TypeBuilder typeBuilder = moduleBuilder.DefineType("MvcAdviceProvider.MvcAdviceProviderType", TypeAttributes.Public, typeof(object), newType[] { typeof(IAssessmentAopAdviceProvider) });

注意第三個和第四個參數,分別是該類型的基類型和實現的接口列表。

然后我們根據傳人的方法名稱和參數定義方法:

    MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Virtual, typeof(string), newType[] { typeof(int) });

DefineMethod方法中,傳人的第一個參數是方法的名稱,第二個參數是訪問類型,第三個參數是修飾符,第四個參數是方法的參數類型列表。這里需要注意第二個參數,也就是方法的修飾符,因為接口中的方法定義都是virtual的,所以在實現接口的時候,方法也必須聲明為MethodAttributes.Virtual

接下來定義方法的參數,使用MethodBuilder.DefineParameter方法。

                beforeMethodBuilder.DefineParameter(1, ParameterAttributes.None ,"value");

DefineParameter方法的第一參數指定當前定義的參數在方法參數列表中的順序,從1開始,如果設置為0則代表方法的返回參數。第二個參數是設置參數的特性,如輸入參數,輸出參數等等。第三個參數是指定該參數的名稱。

方法定義完畢,接下來就是發出Opcode,返回一個指定的字符串。先獲取ILGenerator實例,如下:

ILGenerator generator1 = beforeMethodBuilder.GetILGenerator();

我們若想返回一個字符串,必須先為該字符串聲明一個局部變量,可以使用LocalBuilder.DeclareLocal方法,如下:

    LocalBuilder local1=  generator1.DeclareLocal(typeof(string));

              local1.SetLocalSymInfo("param1");

ocal1.SetLocalSymInfo("param1")指定局部變量的名稱。

需要注意,如果模塊定義時不允許發出符號信息,是無法使用SetLocalSymInfo方法的,AdviceProviderFactory的構造函數中,我們定義模塊的代碼

moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProviderModule", "test.dll",true);

最后一個參數是指定是否允許發出符號信息的。

發出的Opcode很簡單:

   generator1.Emit(OpCodes.Nop);

                generator1.Emit(OpCodes.Ldstr, returnValue);

                generator1.Emit(OpCodes.Stloc_0);

                generator1.Emit(OpCodes.Ldloc_0);

                generator1.Emit(OpCodes.Ret);

第一條指令不執行任何操作。

第二條指令加載一個字符串到計算堆棧中。

第三條指令賦值計算堆棧頂部的數據到局部變量列表中的第一個變量。

第四條指令加載局部變量列表中的第一個變量的數據引用到計算堆棧。

第五條指令方法返回。

整個Emit的過程結束了,接下來要創建實例:

   Type providerType = typeBuilder.CreateType();

                assemblyBuilder.Save("test.dll");

              IAssessmentAopAdviceProvider provider = Activator.CreateInstance(providerType) asIAssessmentAopAdviceProvider;

在上面的代碼中,我們保存了模塊,使用反編譯工具加載該模塊,看看生成的代碼是不是預期的。Il代碼如下:

class public auto ansi MvcAdviceProvider.MvcAdviceProviderType
    
extends [mscorlib]System.Object
    
implements [EmitMock]EmitMock.IAssessmentAopAdviceProvider
{
    
// Methods
    
.method public virtual 
        
instance string Before (
            
int32 'value'
        ) 
cil managed 
    {
        
// Method begins at RVA 0x2050
        
// Code size 9 (0x9)
        
.maxstack 1
        
.locals init (
            [0] 
string
        )

        IL_0000: 
nop
        IL_0001: 
ldstr "HelloWorld!"
        IL_0006: 
stloc.0
        IL_0007: 
ldloc.0
        IL_0008: 
ret
    } 
// end of method MvcAdviceProviderType::Before

    
.method public specialname rtspecialname 
        
instance void .ctor () cil managed 
    {
        
// Method begins at RVA 0x2068
        
// Code size 7 (0x7)
        
.maxstack 2

        IL_0000: 
ldarg.0
        IL_0001: 
call instance void [mscorlib]System.Object::.ctor()
        IL_0006: 
ret
    } 
// end of method MvcAdviceProviderType::.ctor

// end of class MvcAdviceProvider.MvcAdviceProviderType

c#代碼如下:

using EmitMock;
using System;
namespace MvcAdviceProvider
{
    
public class MvcAdviceProviderType : IAssessmentAopAdviceProvider
    {
        
public  string Before(int value)
        {
            
return "HelloWorld!";
        }
    }
}

最后,編寫一個控制台程序來測試一下:

      staticvoid Main(string[] args)

        {

            EmitMock.Mock<IAssessmentAopAdviceProvider> mocker = newMock<IAssessmentAopAdviceProvider>();

            mocker.Setup(t => t.Before(3)).Returns("HelloWorld!");

            Console.WriteLine(mocker.Obj.Before(2));

          Console.Read();

        }

運行結果如下圖:

 

在下一篇博客,不准備繼續介紹Emit的應用,在抱怨Emit的繁瑣之余,是否還有其他選擇呢?我們來談一談《EmitMono.cecil》。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM