只要寫過幾年C#诺苹,你就不可能沒用過反射咕晋。
如果要通過反射調(diào)用函數(shù),最廣泛的用法是使用Invoke收奔。
比Invoke高效一些的做法是使用表達(dá)式預(yù)先編譯一個(gè)委托掌呜,之后通過委托進(jìn)行調(diào)用。
我此前做過一個(gè)粗略的測(cè)試來對(duì)比函數(shù)的直接調(diào)用坪哄、表達(dá)式委托质蕉、Invoke調(diào)用三者之間的性能差異。發(fā)現(xiàn)表達(dá)式方式比直接調(diào)用慢70倍翩肌,Invoke比直接調(diào)用慢2000倍模暗。
當(dāng)然,這只是在我的機(jī)器上跑出來的數(shù)據(jù)念祭,不同的機(jī)器和不同的Framework版本可能會(huì)有不同的結(jié)果兑宇。
但是無論如何,可以看到Invoke方式是有很大的性能損失的粱坤。
為了挽救這種性能損失隶糕,可以考慮使用Emit技術(shù)祝钢。
Emit可以使用MSIL指令動(dòng)態(tài)編寫程序邏輯,然后將指令編譯成程序集若厚。
之后執(zhí)行起來就跟原生調(diào)用沒什么區(qū)別了拦英,自然也就不會(huì)有太大的性能損失,這樣既保證了靈活性测秸,又保證了程序性能疤估。
來一個(gè)最簡(jiǎn)單的示例,感受一下Emit的用法:
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace EmitDemo
{
public interface IConsole
{
void Say();
}
class Example1
{
public static void Run()
{
// 在看下面的代碼之前霎冯,先明白這個(gè)依賴關(guān)系铃拇,即:
// 方法->類型->模塊->程序集
//定義程序集的名稱
AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
// 創(chuàng)建一個(gè)程序集構(gòu)建器
// Framework應(yīng)該這樣:AppDomain.CurrentDomain.DefineDynamicAssembly
AssemblyBuilder ab =
AssemblyBuilder.DefineDynamicAssembly(
aName,
AssemblyBuilderAccess.Run);
// 使用程序集構(gòu)建器創(chuàng)建一個(gè)模塊構(gòu)建器
ModuleBuilder mb = ab.DefineDynamicModule(aName.Name + ".dll");
// 使用模塊構(gòu)建器創(chuàng)建一個(gè)類型構(gòu)建器
TypeBuilder tb = mb.DefineType("DynamicConsole");
// 使類型實(shí)現(xiàn)IConsole接口
tb.AddInterfaceImplementation(typeof(IConsole));
var attrs = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig | MethodAttributes.Final;
// 使用類型構(gòu)建器創(chuàng)建一個(gè)方法構(gòu)建器
MethodBuilder methodBuilder = tb.DefineMethod("Say", attrs, typeof(void), Type.EmptyTypes);
// 通過方法構(gòu)建器獲取一個(gè)MSIL生成器
var IL = methodBuilder.GetILGenerator();
// 開始編寫方法的執(zhí)行邏輯
// 將一個(gè)字符串壓入棧頂
IL.Emit(OpCodes.Ldstr, "I'm here.");
// 調(diào)用Console.Writeline函數(shù)
IL.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));
// 退出函數(shù)
IL.Emit(OpCodes.Ret);
//方法結(jié)束
// 從類型構(gòu)建器中創(chuàng)建出類型
var dynamicType = tb.CreateType();
// 通過反射創(chuàng)建出動(dòng)態(tài)類型的實(shí)例
var console = Activator.CreateInstance(dynamicType) as IConsole;
console.Say();
Console.ReadLine();
}
}
}
這段代碼在.net core下可正常運(yùn)行, 在Framework下有部分代碼需要調(diào)整沈撞,我已在注釋中給出備注慷荔。
代碼略長(zhǎng),不過概括起來說缠俺,這段代碼主要做了這些事情:
動(dòng)態(tài)的創(chuàng)建了一個(gè)程序集显晶,然后在程序集中定義了模塊和類。然后讓這個(gè)類實(shí)現(xiàn)了IConsole接口壹士,之后實(shí)現(xiàn)了Say方法磷雇。Say方法調(diào)用了靜態(tài)方法“Console.WriteLine”,最終輸出了一段字符躏救。
示例程序最核心的代碼是第48到59行唯笙,這幾句代碼使用MSIL指令編寫了一段函數(shù)邏輯,理解這些指令是掌握Emit的關(guān)鍵盒使。
通過上面這個(gè)示例可以習(xí)得動(dòng)態(tài)創(chuàng)建程序集的基本步驟崩掘,即:
定義Assembly,在Assembly中定義Module少办,在Module中定義Type苞慢,在Type中定義方法,之后使用IL編寫方法的執(zhí)行邏輯凡泣。
下面再來看一個(gè)復(fù)雜一些的示例枉疼,更多的了解一些常用的IL指令的用法。
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace EmitDemo
{
class Example2
{
public static void Run()
{
AssemblyName aName = new AssemblyName("ChefDynamicAssembly");
AssemblyBuilder ab =
AssemblyBuilder.DefineDynamicAssembly(
aName,
AssemblyBuilderAccess.Run);
ModuleBuilder mb = ab.DefineDynamicModule(aName.Name + ".dll");
TypeBuilder tb = mb.DefineType("Commander");
var attrs = MethodAttributes.Public;
// 使用類型構(gòu)建器創(chuàng)建一個(gè)方法構(gòu)建器
MethodBuilder methodBuilder = tb.DefineMethod("Do", attrs, typeof(string), Type.EmptyTypes);
// 通過方法構(gòu)建器獲取一個(gè)MSIL生成器
var IL = methodBuilder.GetILGenerator();
// 開始編寫方法的執(zhí)行邏輯
// var vegetables = new string[3];
var vegetables = IL.DeclareLocal(typeof(string[]));
IL.Emit(OpCodes.Ldc_I4, 3);
IL.Emit(OpCodes.Newarr, typeof(string));
IL.Emit(OpCodes.Stloc, vegetables);
//vegetables[0] = "土豆"
IL.Emit(OpCodes.Ldloc, vegetables);
IL.Emit(OpCodes.Ldc_I4, 0);
IL.Emit(OpCodes.Ldstr, "土豆");
IL.Emit(OpCodes.Stelem, typeof(string));
//vegetables[1] = "青椒"
IL.Emit(OpCodes.Ldloc, vegetables);
IL.Emit(OpCodes.Ldc_I4, 1);
IL.Emit(OpCodes.Ldstr, "青椒");
IL.Emit(OpCodes.Stelem, typeof(string));
//vegetables[2] = "木耳"
IL.Emit(OpCodes.Ldloc, vegetables);
IL.Emit(OpCodes.Ldc_I4, 2);
IL.Emit(OpCodes.Ldstr, "木耳");
IL.Emit(OpCodes.Stelem, typeof(string));
// IChef chef = new GoodChef();
var chef = IL.DeclareLocal(typeof(IChef));
IL.Emit(OpCodes.Newobj, typeof(GoodChef).GetConstructor(Type.EmptyTypes));
IL.Emit(OpCodes.Stloc, chef);
//var dish = chef.Cook(vegetables);
var dish = IL.DeclareLocal(typeof(string));
IL.Emit(OpCodes.Ldloc, chef);
IL.Emit(OpCodes.Ldloc, vegetables);
IL.Emit(OpCodes.Callvirt, typeof(IChef).GetMethod("Cook"));
IL.Emit(OpCodes.Stloc, dish);
// return dish;
IL.Emit(OpCodes.Ldloc, dish);
IL.Emit(OpCodes.Ret);
//方法結(jié)束
// 從類型構(gòu)建器中創(chuàng)建出類型
var dynamicType = tb.CreateType();
// 通過反射創(chuàng)建出動(dòng)態(tài)類型的實(shí)例
var commander = Activator.CreateInstance(dynamicType);
Console.WriteLine(dynamicType.GetMethod("Do").Invoke(commander, null).ToString());
Console.ReadLine();
}
}
public interface IChef
{
string Cook(string[] vegetables);
}
public class GoodChef : IChef
{
public string Cook(string[] vegetables)
{
return "good:" + string.Join("+", vegetables);
}
}
}
這個(gè)示例有如下輸出:
good:土豆+青椒+木耳
除開一些基礎(chǔ)設(shè)施的定義鞋拟,以第30至72行為主要關(guān)注點(diǎn)骂维。
代碼其實(shí)沒有什么復(fù)雜的邏輯,就是定義了一個(gè)數(shù)組贺纲,創(chuàng)建一個(gè)實(shí)現(xiàn)了IChef的接口的實(shí)例航闺,將數(shù)組傳遞給實(shí)例的Cook方法,獲得輸出后返回。
不過潦刃,邏輯雖然簡(jiǎn)單侮措,但是卻展示了多種常用的IL指令的使用方法。例如乖杠,創(chuàng)建數(shù)組分扎,給數(shù)組賦值,創(chuàng)建對(duì)象胧洒,調(diào)用實(shí)例方法等等……
每一段IL指令對(duì)應(yīng)的C#語法都已在注釋中進(jìn)行了說明畏吓,建議對(duì)照起來看。
對(duì)于這些指令的具體用法卫漫,建議參考MSDN菲饼。要是MSDN看的云里霧里,可以看一下文章末尾介紹的MSDN查閱指南列赎。
另外宏悦,我再介紹兩點(diǎn)編程技巧:
第一、如果不知道某些語句怎么寫包吝,可以先把你想要寫的邏輯用C#表達(dá)出來之后編譯成DLL饼煞,之后使用微軟提供的ILDAsm(VS自帶)工具打開這個(gè)DLL查看具體的IL指令,然后對(duì)照著翻譯成Emit IL漏策。
第二派哲、如果要驗(yàn)證Emit生成的DLL是否是自己想要表的的邏輯臼氨,可以使用ILSpy反編譯工具將生成的DLL反編譯出來看一看具體的邏輯掺喻。
實(shí)際應(yīng)用中,除了使用Emit來提升反射的執(zhí)行效率外储矩,還可以用它來實(shí)現(xiàn)動(dòng)態(tài)代理或者應(yīng)用到AOP編程技術(shù)中感耙,可謂不得不掌握的C#高級(jí)編程技術(shù)。
MSDN查閱指南之OpCodes
MSDN找到System.Reflection.Emit命名空間持隧,找到OpCodes類即硼,定位到Fields部分。
這里可以全局總覽所有的IL指令屡拨,可以大致瀏覽一下每個(gè)指令的作用只酥。
然后選擇一個(gè)指令進(jìn)入子頁面查看對(duì)該指令的詳細(xì)介紹。
接下來以Call指令為例進(jìn)行介紹呀狼。
好了,正文就啰嗦到這里吧十饥。
在開始認(rèn)真寫文章之前總覺得沒什么可寫的窟勃,但是一旦開始之后總覺得有太多的東西可以寫。
所以說逗堵,思路是需要整理的秉氧。
謝謝觀看,點(diǎn)個(gè)贊唄蜒秤?