之前在上篇博客說到用表達式來替代反射機制,可以獲得較高的性能提升。這篇我們來說說用Emit技術來替代反射。
System.Reflection.Emit命名空間類可用於動態發出Microsoft中間語言(MSIL)代碼,以便生成的代碼可以直接執行。反射也用於獲取有關類及其成員的信息。換句話說,反射是一種技術,允許您檢查描述類型及其成員的元數據,你可能以編程方式訪問過組件對象模型類型庫, .NET中的反射非常相似,但功能強大且易於使用。使用.NET編譯器編譯源文件時,編譯器會產生源文件中語句中的MSIL代碼以及描述文件中定義的類型的元數據。正是這個元數據,.NET中的反射API使你能夠檢查。在這個System.Reflection命名空間中,有一些類可用於幫助訪問程序中固有的結構,比如類、類型、字段、結構、枚舉、成員和方法。例如,您使用Type類來標識所反映的類的類型,FieldInfo類表示結構或枚舉的字段。MemberInfo類表示反射類的成員,並使用MethodInfo類表示反射類的方法。PrimeRealFipe類表示反射類中的方法的參數。
使用System.Reflection.Emit命名空間類在可以編譯時創建代碼,但前提是必須懂IL代碼。(本文不做IL代碼詳解,因為我也不會。。。)事實上,你實際編寫的是就是幕后的MSIL本身。你可以使用反射在內存中定義程序集,為該程序集創建類/模塊,然后為該模塊創建其他模塊成員和新類型。你同樣也可以使用Emit來構造程序集。Reflection.Emit是一個強大的命名空間,我們可以在運行時動態地發出瞬態和持久化程序集。Reflection.Emit產生一個低級,語言中立的MSIL。通常,我們通過將源代碼保存到磁盤然后編譯該源代碼來創建程序集,然后我們調用我們需要從該程序集中使用的類的方法,該程序集是在磁盤上編譯的。但是你可以想象,這涉及額外的磁盤寫入和讀取工作!使用反射生成代碼,我們可以省略此開銷並立即將操作代碼直接發送到內存中。反射發射只不過是直接在代碼中編寫任何匯編代碼,然后即時調用生成的代碼。這也並不是說反射效率就是高,因為在運行期產生指令也是需要時間,各有優缺點。
System.Reflection.Emit命名空間提供用戶動態創建.exe文件所需的類。它的類允許編譯器或工具發出元數據和MSIL。因此,您可以動態地在磁盤上創建.exe文件,就像運行代碼,保存代碼並調用編譯器來編譯代碼一樣。大多數情況下,您需要此功能和此命名空間用於自定義腳本引擎和編譯器。
- AssemblyBuilder類是在運行時發出代碼並具有創建動態模塊的方法的任何應用程序的起點。
- ModuleBuilder類用作在運行時向動態程序集添加類和結構等類型的起點。
本文通過Emit技術來提高后期綁定對象的性能,盡管您不能像硬綁定那樣快速執行調用,但執行效果會比在運行時產生代碼在綁定更好。代碼基本與前篇博客用lambda表達式樹替代反射基本一樣,核心代碼替換過來即可,如下:
public class PropertyEmit { private PropertySetterEmit setter; private PropertyGetterEmit getter; public String PropertyName { get; private set; } public PropertyInfo Info { get; private set; } public PropertyEmit(PropertyInfo propertyInfo) { if (propertyInfo == null) { throw new ArgumentNullException("屬性不能為空"); } if (propertyInfo.CanWrite) { setter = new PropertySetterEmit(propertyInfo); } if (propertyInfo.CanRead) { getter = new PropertyGetterEmit(propertyInfo); } this.PropertyName = propertyInfo.Name; this.Info = propertyInfo; } /// <summary> /// 屬性賦值操作(Emit技術) /// </summary> /// <param name="instance"></param> /// <param name="value"></param> public void SetValue(Object instance,Object value) { this.setter?.Invoke(instance, value); } /// <summary> /// 屬性取值操作(Emit技術) /// </summary> /// <param name="instance"></param> /// <returns></returns> public Object GetValue(Object instance) { return this.getter?.Invoke(instance); } private static readonly ConcurrentDictionary<Type, PropertyEmit[]> securityCache = new ConcurrentDictionary<Type, PropertyEmit[]>(); /// <summary> /// 獲取對象屬性 /// </summary> /// <param name="type">對象類型</param> /// <returns></returns> public static PropertyEmit[] GetProperties(Type type) { return securityCache.GetOrAdd(type, t => t.GetProperties().Select(p => new PropertyEmit(p)).ToArray()); } } /// <summary> /// Emit 動態構造 Get方法 /// </summary> public class PropertyGetterEmit { private readonly Func<Object, Object> getter; public PropertyGetterEmit(PropertyInfo propertyInfo) { //Objcet value = Obj.GetValue(Object instance); if (propertyInfo == null) { throw new ArgumentNullException("propertyInfo"); } this.getter = CreateGetterEmit(propertyInfo); } public Object Invoke(Object instance) { return getter?.Invoke(instance); } private Func<Object, Object> CreateGetterEmit(PropertyInfo property) { if (property == null) throw new ArgumentNullException("property"); MethodInfo getMethod = property.GetGetMethod(true); DynamicMethod dm = new DynamicMethod("PropertyGetter", typeof(Object), new Type[] { typeof(Object) }, property.DeclaringType, true); ILGenerator il = dm.GetILGenerator(); if (!getMethod.IsStatic) { il.Emit(OpCodes.Ldarg_0); il.EmitCall(OpCodes.Callvirt, getMethod, null); } else il.EmitCall(OpCodes.Call, getMethod, null); if (property.PropertyType.IsValueType) il.Emit(OpCodes.Box, property.PropertyType); il.Emit(OpCodes.Ret); return (Func<Object, Object>)dm.CreateDelegate(typeof(Func<Object, Object>)); } } /// <summary> /// Emit動態構造Set方法 /// </summary> public class PropertySetterEmit { private readonly Action<Object, Object> setFunc; public PropertySetterEmit(PropertyInfo propertyInfo) { //Obj.Set(Object instance,Object value) if (propertyInfo == null) { throw new ArgumentNullException("propertyInfo"); } this.setFunc = CreatePropertySetter(propertyInfo); } private Action<Object, Object> CreatePropertySetter(PropertyInfo property) { if (property == null) throw new ArgumentNullException("property"); MethodInfo setMethod = property.GetSetMethod(true); DynamicMethod dm = new DynamicMethod("PropertySetter", null, new Type[] { typeof(Object), typeof(Object) }, property.DeclaringType, true); ILGenerator il = dm.GetILGenerator(); if (!setMethod.IsStatic) { il.Emit(OpCodes.Ldarg_0); } il.Emit(OpCodes.Ldarg_1); EmitCastToReference(il, property.PropertyType); if (!setMethod.IsStatic && !property.DeclaringType.IsValueType) { il.EmitCall(OpCodes.Callvirt, setMethod, null); } else il.EmitCall(OpCodes.Call, setMethod, null); il.Emit(OpCodes.Ret); return (Action<Object, Object>)dm.CreateDelegate(typeof(Action<Object, Object>)); } private static void EmitCastToReference(ILGenerator il, Type type) { if (type.IsValueType) il.Emit(OpCodes.Unbox_Any, type); else il.Emit(OpCodes.Castclass, type); } public void Invoke(Object instance,Object value) { this.setFunc?.Invoke(instance, value); } }
與表達式一起對比,其測試代碼如下:
Student student = new Student(); //學生對象,里面有一個Name屬性 PropertyInfo propertyInfo = student.GetType().GetProperty(nameof(student.Name)); Property PropertyExp = new Property(propertyInfo); PropertyEmit propertyEmit = new PropertyEmit(propertyInfo); Int32 loopCount = 1000000; //執行次數 CodeTimer.Initialize(); //測試環境初始化 CodeTimer.Time("基礎反射", loopCount, () => { propertyInfo.SetValue(student, "Fode",null); }); CodeTimer.Time("lambda表達式樹", loopCount, () => { PropertyExp.SetValue(student, "Fode"); }); CodeTimer.Time("Emit",loopCount,()=> { propertyEmit.SetValue(student, "Fode"); }); CodeTimer.Time("直接賦值", loopCount, () => { student.Name = "Fode"; }); Console.ReadKey();
測試效果圖如下:表達式與Emit速度基本相同,將我上述的方法CreatePropertySetter改成靜態會比表達式快一點。在使用的過程中,最好將其封裝成一個靜態泛型類緩存起來,一直new PropertyEmit這個對象反而效率會很低。代碼下載。
文章結尾在分享幾個我認為寫得不錯,可能對大家有幫助的文章:
C# 之 反射性能優化1
Emit常用Opcodes指令使用方法(含實例)