Emit學習-基礎篇-基本概念介紹

  之前的Hello World例子應該已經讓我們對Emit有了一個模糊的了解,那么Emit到底是什么樣一個東西,他又能實現些什么功能呢?昨天查了點資料,大致總結了下,由于才開始學習肯定有不完善的地方,希望大家能夠批評指正。

1.?? ? ??什么是反射發出(Reflection Emit

Emit應該是屬于反射中的一個比較高級的功能,說到反射大家應該都不陌生,反射是在運行時發現對象的相關信息,并且執行這些對象(創建對象實例,執行對象上的方法)。這個功能是由.NETSystem.Reflection命名空間的類所提供的。簡單的說,它們不僅允許你瀏覽一個程序集暴露的類、方法、屬性和字段,而且還允許你創建一個類型的實例以及執行這些類型上的方法(調用成員)。這些特性對于在運行時對象發現,已經很了不起了,但.NET的反射機制并沒有到此結束。反射還允許你在運行時構建一個程序集,并且可以創建全新的類型。這就是反射發出(reflection emit)。

使用Emit可以從零開始,動態的構造程序集和類型,在需要時動態的生成代碼,提高程序的靈活性。有了這些功能,我們可以用其來實現一些典型的應用,如:

l? 動態代理(AOP);

l? 減少反射的性能損失(Dynamic Method等);

l? ORM的實現;

l? 工具及IDE插件的開發;

l? 公共代碼安全模塊的開發。

2.?? ? ??使用Emit的完整流程

使用Emit一般包括以下步驟:

1)??????? 創建一個新的程序集(可以選擇存在與內存中或者持久化到硬盤);

2)??????? 在程序集內創建一個模塊;

3)??????? 在模塊內創建動態類;

4)??????? 給動態類添加動態方法、屬性、事件,等;

5)??????? 生成相關的IL代碼;

6)??????? 返回創建出來的類型或持久化到硬盤中。

當然如果你只是想要創建一個Dynamic Method 那么可以直接使用之前HelloWorld例子中使用的DynamicMethod類來創建一個動態方法,并在構造函數時傳入它所依附的類或者模塊。看了這個流程,相信大家已經對用使用Emit來創建動態類型的過程有了一個直觀的認識,下面我們就通過實現一個求斐波那契數列的類來加深對這一流程的了解。

在開始我們的例子之前,先給大家介紹一款反編譯軟件Reflector使用這個軟件可以給我們編寫IL代碼提供很大的幫助。

接下來我們按照上面所說的流程來創建我們的斐波那契類:

第一步:構建程序集

要構建一個動態的程序集,我們需要創建一個AssemblyBuilder對象,AssemblyBuilder類是整個反射發出工作的基礎,它為我們提供了動態構造程序集的入口。要創建一個AssemblyBuilder對象,需要使用AppDomainDefineDynamicAssembly方法,該方法包括兩個最基本的參數:AssemblyNameAssemblyBuilderAccess前者用來唯一標識一個程序集,后者用來表示動態程序集的訪問方式,有如下的成員:

成員名稱

說明

Run

表示可以執行但不能保存此動態程序集。

Save

表示可以保存但不能執行此動態程序集。

RunAndSave

表示可以執行并保存此動態程序集。

ReflectionOnly

表示在只反射上下文中加載動態程序集,且不能執行此程序集。

在這里我們選擇使用RunAndSave,完整的代碼如下:

ContractedBlock.gifExpandedBlockStart.gifStep 1 構建程序集
#region?Step?1?構建程序集
//創建程序集名
AssemblyName?asmName?=?new?AssemblyName("EmitExamples.DynamicFibonacci");

//獲取程序集所在的應用程序域
//你也可以選擇用AppDomain.CreateDomain方法創建一個新的應用程序域
//這里選擇當前的應用程序域
AppDomain?domain?=?AppDomain.CurrentDomain;

//實例化一個AssemblyBuilder對象來實現動態程序集的構建
AssemblyBuilder?assemblyBuilder?=?domain.DefineDynamicAssembly(asmName,?AssemblyBuilderAccess.RunAndSave);
#endregion

第二步:定義模塊(Module

與第一步類似,要定一個動態模塊,我們需要創建一個ModuleBuilder對象,通過AssemblyBuilder對象的DefineDynamicModule方法,需要傳入模塊的名字(如果要持久化到硬盤,那么還需要傳入要保存的文件的名字,這里就是我們的程序集名),這里我們使用程序集名作為模塊名字:

ContractedBlock.gifExpandedBlockStart.gifStep 2 定義模塊
#region?Step?2?定義模塊
ModuleBuilder?moduleBuilder?
=?assemblyBuilder.DefineDynamicModule(name,?asmFileName);

????? 第三部:創建一個動態類型

這個時候恐怕我不說你也已經知道了,對,現在我們就是要用ModuleBuilder來創建一個TypeBuilder的對象,如下:

ContractedBlock.gifExpandedBlockStart.gifStep 3 定義類型
#region?Step?3?定義類型
TypeBuilder?typeBuilder?
=?moduleBuilder.DefineType("EmitExamples.DynamicFibonacci",?TypeAttributes.Public);
#endregion

這里EmitExamples表示名字空間,DynamicFibonacci是類的名字,TypeAttributes表示類的屬性,可以按照實際需要進行組合。

第四步:定義方法

到這里為止,我們的準備工作已經差不多了,下面要開始真正的大展拳腳啦!

我們先來看一下我們接下來要實現的動態類C#代碼的實現,然后再以這為目標進行動態構建:

ContractedBlock.gifExpandedBlockStart.gifFibonacci
public?class?Fibonacci
{
????
public?int?Calc(int?num)
????{
????????
if?(num?==?1?||?num?==?2)
????????{
????????????
return?1;
????????}
????????
else
????????{
????????????
return?Calc(num?-?1)?+?Calc(num?-?2);
????????}
????}
}

OK,從上面的代碼可以看出我們需要創建一個名為CalcPublic方法,它具有一個Int32型的傳入參數和返回值。同樣的,我們使用TypeBuilderDefineMethod方法來創建這樣一個MethodBuilder,如下:

ContractedBlock.gifExpandedBlockStart.gifStep 4 定義方法
#region?Step?4?定義方法

MethodBuilder?methodBuilder?
=?typeBuilder.DefineMethod(
????
"Calc",?
????MethodAttributes.Public,?
????
typeof(Int32),?
????
new?Type[]?{?typeof(Int32)?});

#endregion

DefineMethod方法的四個參數分別是函數名,修飾符,返回值類型,傳入參數的類型數組。

第五步:實現方法

現在就要為之前創建的Calc方法添加對應的IL代碼了,這對我們這些新手來說這就顯的有點無從入手來了,不過沒關系,還記得我之前提到的那個反編譯工具嗎?現在就是它發揮作用的時候了,我們用它來反編譯之前寫的Fibonacci類,看看自動生成的IL代碼是什么樣的,結果如下:

ContractedBlock.gifExpandedBlockStart.gifIL
.method?public?hidebysig?instance?int32?Calc(int32?num)?cil?managed
{
????.maxstack?
4
????.locals?init?(
????????[
0]?int32?CS$1$0000,
????????[
1]?bool?CS$4$0001)
????L_0000:?nop?
????L_0001:?ldarg.
1?
????L_0002:?ldc.i4.
1?
????L_0003:?beq.s?L_000e
????L_0005:?ldarg.
1?
????L_0006:?ldc.i4.
2?
????L_0007:?ceq?
????L_0009:?ldc.i4.
0?
????L_000a:?ceq?
????L_000c:?br.s?L_000f
????L_000e:?ldc.i4.
0?
????L_000f:?stloc.
1?
????L_0010:?ldloc.
1?
????L_0011:?brtrue.s?L_0018
????L_0013:?nop?
????L_0014:?ldc.i4.
1?
????L_0015:?stloc.
0?
????L_0016:?br.s?L_002f
????L_0018:?nop?
????L_0019:?ldarg.
0?
????L_001a:?ldarg.
1?
????L_001b:?ldc.i4.
1?
????L_001c:?sub?
????L_001d:?call?instance?int32?EmitExamples.Fibonacci::Calc(int32)
????L_0022:?ldarg.
0?
????L_0023:?ldarg.
1?
????L_0024:?ldc.i4.
2?
????L_0025:?sub?
????L_0026:?call?instance?int32?EmitExamples.Fibonacci::Calc(int32)
????L_002b:?add?
????L_002c:?stloc.
0?
????L_002d:?br.s?L_002f
????L_002f:?ldloc.
0?
????L_0030:?ret?
}

我們來對上面的IL代碼進行分析:

l? L_0000L_0003是加載參數一、加載整數1,然后判斷兩者是否相等,如果相等則跳轉到L_000e繼續執行;

l? L_0005L_000e是加載參數一、加載整數2,然后判斷兩者是否相等,如果相等則將整數1送到堆棧上,否則將整數0送到堆棧上;然后再加載整數0,用之前比較的結果和0進行比較,如果相等則將整數1送到堆棧上,否則將整數0送到堆棧上;這個時侯,如果傳入的參數是2那么現在堆棧上的數字就是兩個0,兩者相等,那么跳轉到L_000f繼續執行,反之就繼續執行,加載數字0到堆棧上(是不是感覺很復雜,沒關系,我們一會對其進行優化);

l? L_000fL_0016是判斷之前判斷的返回值,也就是說如果傳入的參數是1或者2,那么就將局部變量0的值設為1,然后跳轉到L_002f執行;反之就從L_0018開始執行;

l? L_0018L_002b是把參數0和參數1加載(注意:在非靜態方法中,參數0表示其對自身所在類的示例的引用,相當于this),然后將參數1分別減去12后進行遞歸調用,并將結果相加,并把記過放到局部變量0中;

l? L_002dL_0030是加載局部變量0,并將結果返回。

有了之前分析的基礎,我們可以將流程簡化為如下步驟:

1)??????? 如果傳入的參數是1,跳轉到第六步執行;

2)??????? 如果傳入的參數是2,跳轉到第六步執行;

3)??????? 將傳入的參數減1,然后遞歸調用自身;

4)??????? 將傳入的參數減2,然后遞歸調用自身;

5)??????? 將遞歸調用的結果相加,跳轉到第七步執行;

6)??????? 設置堆棧頂的值為1

7)??????? 返回堆棧頂的元素作為結果。

然后我們就可以參照以上的反編譯出來的IL代碼,用Emit書寫出對應的IL代碼,具體代碼如下:

ContractedBlock.gifExpandedBlockStart.gifStep 5 實現方法
#region?Step?5?實現方法

ILGenerator?calcIL?
=?methodBuilder.GetILGenerator();

//定義標簽lbReturn1,用來設置返回值為1
Label?lbReturn1?=?calcIL.DefineLabel();
//定義標簽lbReturnResutl,用來返回最終結果
Label?lbReturnResutl?=?calcIL.DefineLabel();

//加載參數1,和整數1,相比較,如果相等則設置返回值為1
calcIL.Emit(OpCodes.Ldarg_1);
calcIL.Emit(OpCodes.Ldc_I4_1);
calcIL.Emit(OpCodes.Beq_S,?lbReturn1);

//加載參數1,和整數2,相比較,如果相等則設置返回值為1
calcIL.Emit(OpCodes.Ldarg_1);
calcIL.Emit(OpCodes.Ldc_I4_2);
calcIL.Emit(OpCodes.Beq_S,?lbReturn1);

//加載參數0和1,將參數1減去1,遞歸調用自身
calcIL.Emit(OpCodes.Ldarg_0);
calcIL.Emit(OpCodes.Ldarg_1);
calcIL.Emit(OpCodes.Ldc_I4_1);
calcIL.Emit(OpCodes.Sub);
calcIL.Emit(OpCodes.Call,?methodBuilder);

//加載參數0和1,將參數1減去2,遞歸調用自身
calcIL.Emit(OpCodes.Ldarg_0);
calcIL.Emit(OpCodes.Ldarg_1);
calcIL.Emit(OpCodes.Ldc_I4_2);
calcIL.Emit(OpCodes.Sub);
calcIL.Emit(OpCodes.Call,?methodBuilder);

//將遞歸調用的結果相加,并返回
calcIL.Emit(OpCodes.Add);
calcIL.Emit(OpCodes.Br,?lbReturnResutl);

//在這里創建標簽lbReturn1
calcIL.MarkLabel(lbReturn1);
calcIL.Emit(OpCodes.Ldc_I4_1);

//在這里創建標簽lbReturnResutl
calcIL.MarkLabel(lbReturnResutl);
calcIL.Emit(OpCodes.Ret);???????

#endregion

第六步:創建類型,并持久化到硬盤

到上一步為止,我們已經完成了斐波那契類以及方法的完整創建,接下來就是收獲的時候了,我們使用TypeBuilderCreateType方法完成最終的創建過程;最后使用AssemblyBuilder類的Save方法將程序集持久化到硬盤中,代碼如下:

ContractedBlock.gifExpandedBlockStart.gifStep 6 收獲
#region?Step?6?收獲

Type?type?
=?typeBuilder.CreateType();

assemblyBuilder.Save(asmFileName);

object?ob?=?Activator.CreateInstance(type);

for?(int?i?=?1;?i?<?10;?i++)
{
????Console.WriteLine(type.GetMethod(
"Calc").Invoke(ob,?new?object[]?{?i?}));
}

#endregion

?

這里使用Activator.CreateInstance方法創建了動態類型的一個實例,然后使用MethodInfoInvoke方法調用里里面的Calc方法,看起來需要通過多次反射,好像性能并不是很好,但其實我們完全可以用Emit來替代掉這兩個方法,將反射帶來的性能影響降到最低,這個將在以后講到。最后在這里提供源程序的下載?Fibonacci

?

?

轉載于:https://www.cnblogs.com/yingql/archive/2009/03/22/1418941.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/news/379717.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/379717.shtml
英文地址,請注明出處:http://en.pswp.cn/news/379717.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

The FreeRTOS Distribution(介紹、移植、類型定義)

1 Understand the FreeRTOS Distribution 1.1 Definition &#xff1a;FreeRTOS Port FreeRTOS目前可以在20種不同的編譯器構建&#xff0c;并且可以在30多種不同的處理器架構上運行&#xff0c;每個受支持的編譯器和處理器組合被認為是一個單獨的FreeRTOS Port。 1.2 Build…

notepad++節點_在C ++中刪除鏈接列表的中間節點

notepad節點Given a single Linked List and we have to delete the middle the element of the Linked List. 給定一個鏈表&#xff0c;我們必須刪除鏈表中間的元素。 If the length of the linked list is odd then delete (( n1)/2)th term of the linked list and if the…

SET ANSI_NULLS ON

指定在與 Null 值一起使用等于 () 和不等于 (<>) 比較運算符時采用符合 ISO 標準的行為。 當 SET ANSI_NULLS 為 ON 時&#xff0c;即使 column_name 中包含空值&#xff0c;使用 WHERE column_name NULL 的 SELECT 語句仍返回零行。即使 column_name 中包含非空值&…

Eclipse項目左上角出現大紅色感嘆號怎么辦?

出現大紅色感嘆號是因為環境不匹配 解決方法&#xff1a; 右擊出現大紅色感嘆號的項目 點擊 Libraries&#xff0c;將有叉號的給Remove掉 然后再點擊 Add Library —> JRE System Library —> Next 勾選第二個即可 之后&#xff0c;就不會出現大紅色感嘆號了。

PCB---STM32最小系統制作過程

PCB 制作過程STM32核心模塊連接外部電源晶振OSC_IN(8MHz)OSC32_IN(32.768MHz&#xff09;復位下載口BOOT模式電源模塊添加功能UARTWKUPSTM32核心模塊 這里我們以STM32F103C8T6為列&#xff0c;先將芯片的原理圖放到原理圖中 對于STM32&#xff0c;有幾個模塊是核心&#xff0…

scala 隨機生成整數_如何在Scala中以整數形式獲取當前年份?

scala 隨機生成整數In Scala programming language, there is an option for the programmer to use libraries of java because of its interoperability with java. 在Scala編程語言中&#xff0c;程序員可以選擇使用Java庫&#xff0c;因為它可以與Java互操作。 There are …

轉載:glut.h 與 stdlib.h中 的exit()重定義問題的解決

遇到的問題&#xff0c;來自&#xff1a;http://blog.sina.com.cn/s/blog_629c53bd0100f5li.html 出現&#xff1a; c:\codeprogram\microsoft visual studio 10.0\vc\include\stdlib.h(353): error C2381: “exit”: 重定義&#xff1b;__declspec(noreturn) 不同1> c:\pro…

括號配對問題(C++棧)

題目描述: 現在&#xff0c;有一行括號序列&#xff0c;請你檢查這行括號是否配對。 輸入描述: 第一行輸入一個數N&#xff08;0<N<100&#xff09;,表示有N組測試數據。后面的N行輸入多組輸入數據&#xff0c;每組輸入數據都是一個字符串S(S的長度小于10000&#xff0c;…

FreeRTOS---堆內存管理(一)

FreeRTOS的堆內存管理簡介動態內存分配及其與 FreeRTOS 的相關性動態內存分配選項內存分配方案Heap_1heap_2Heap_3Heap_4設置heap_4的起始地址Heap_5vPortDefineHeapRegions()堆相關的函數xPortGetFreeHeapSizexPortGetMinimumEverFreeHeapSizeMalloc調用失敗的Hook函數這篇文章…

python中生成隨機整數_在Python中生成0到9之間的隨機整數

python中生成隨機整數Following are the few explanatory illustrations using different python modules, on how to generate random integers? Consider the scenario of generating the random numbers between 0 and 9 (both inclusive). 以下是使用不同的python模塊的一…

愚人節惡搞網站謹防遭黑客攻擊

金山毒霸云安全中心日前發出預警&#xff0c;在近期攔截的大量“掛馬”、釣魚等惡意網頁中&#xff0c;與“愚人節”相關的&#xff0c;在近一周數量急劇增加。 愚人節將至&#xff0c;怎么整人好玩?近期許多惡搞網站、相關的網絡論壇的流量不斷攀升。金山毒霸云安全中心日前發…

JavaScript中的String()函數與示例

String()函數 (String() function) String() function is a predefined global function in JavaScript, it is used to convert an object to the string. String()函數是JavaScript中預定義的全局函數&#xff0c;用于將對象轉換為字符串。 Example: 例&#xff1a; In thi…

ASCII碼排序(C++)

題目描述: 輸入三個字符&#xff08;可以重復&#xff09;后&#xff0c;按各字符的ASCII碼從小到大的順序輸出這三個字符。 輸入描述: 第一行輸入一個數N,表示有N組測試數據。后面的N行輸入多組數據&#xff0c;每組輸入數據都是占一行&#xff0c;有三個字符組成&#xff0c;…

FreeRTOS--堆內存管理(二)

堆內存管理代碼具體實現heap_1內存申請函數內存釋放函數heap_2內存塊內存堆初始化函數內存塊插入函數內存申請函數判斷是不是第一次申請內存開始分配內存內存釋放函數heap_3heap_4內存堆初始化函數內存塊插入函數heap_5上一篇文章說了FreeRTOS實現堆內存的原理&#xff0c;這一…

在查詢的結果中添加自增列 兩種方法

解決辦法《一》&#xff1a; 在SQL Server數據庫中表信息會用到Identity關鍵字來設置自增列。但是當有數據被刪除的話&#xff0c;自增列就不連續了。如果想查詢出這個表的信息&#xff0c;并添 加一列連續自增的ID&#xff0c;可用如下查詢語句&#xff1a; SELECT Row_Nu…

一個非常簡單的C#面試題

怎樣實現對所有類可讀但是在同一個assembly可寫那&#xff1f; 答案&#xff1a; 同一個assembly namespace ClassLibrary1 { public class Class1 { public string Name { get; internal set; } } public class Class2 { public void GS() { Class1 cc new Class1(); cc.Name…

css中的node.js_在Node App中使用基本HTML,CSS和JavaScript

css中的node.jsYou may think this is not important, but it is!. As a beginner in node.js, most coding exercises are always server sided. 您可能認為這并不重要&#xff0c;但確實如此&#xff01; 作為node.js的初學者&#xff0c;大多數編碼練習始終都是服務器端的。…

Binary String Matching(C++)

題目描述: Given two strings A and B, whose alphabet consist only ‘0’ and ‘1’. Your task is only to tell how many times does A appear as a substring of B? For example, the text string B is ‘1001110110’ while the pattern string A is ‘11’, you should…

由一次代碼優化想到的Js 數據類型

引子&#xff1a; 上周三進行了代碼優化&#xff0c;其中有一個很普遍的代碼&#xff0c;例如&#xff1a; if(test "") {dothis();}else{dothat()} ----->可以簡化為 !test ? dothis():dothat(); if(test "") {dothis()}     ----->可以簡化為…

VisualStudio2019配置OpenCV

VisualStudio2019配置OpenCV配置0x01 準備0x02 配置系統環境0x03 復制文件0x04 配置VisualStudio2019測試配置 0x01 準備 下載opencv&#xff0c;官網地址&#xff1a;https://opencv.org/releases/# 下載之后&#xff0c;自行安裝 0x02 配置系統環境 找到高級系統設置 …