在频繁的通过反射来设置和获取属性的值时是比较耗时的,本章通过Emit技术优化反射来提高获取和设置属性值的效率
一、实现代码:
/// <summary> /// 设置器委托 /// </summary> /// <param name="target"></param> /// <param name="arg"></param> public delegate void SetValueDelegate(object target, object arg); /// <summary> /// 访问器委托 /// </summary> /// <param name="target">目标对象</param> /// <returns></returns> public delegate object GetValueDelegate(object target); public static class DynamicMethodFactory { /// <summary> /// 获取访问器 /// </summary> /// <param name="property"></param> /// <returns></returns> public static GetValueDelegate GetGetter(this PropertyInfo property) { return DynamicMethodFactory.CreatePropertyGetter(property); } /// <summary> /// 获取设置器 /// </summary> /// <param name="property"></param> /// <returns></returns> public static SetValueDelegate GetSetter(this PropertyInfo property) { return DynamicMethodFactory.CreatePropertySetter(property); } private static SetValueDelegate CreatePropertySetter(PropertyInfo property) { if (property == null) { throw new ArgumentNullException("property"); } if (!property.CanWrite) { return null; } 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 (SetValueDelegate)dm.CreateDelegate(typeof(SetValueDelegate)); } private static GetValueDelegate CreatePropertyGetter(PropertyInfo property) { if (property == null) { throw new ArgumentNullException("property"); } if (!property.CanRead) { return null; } MethodInfo getMethod = property.GetGetMethod(true); DynamicMethod dm = new DynamicMethod("PropertyGetter", typeof(object), new[] { typeof(object) }, property.DeclaringType, true); Type returnType = getMethod.ReturnType; ILGenerator il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); EmitCastToReference(il, getMethod.DeclaringType); if (getMethod.IsFinal) { il.Emit(OpCodes.Call, getMethod); } else { il.Emit(OpCodes.Callvirt, getMethod); } if (returnType.IsValueType) { il.Emit(OpCodes.Box, returnType); } il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret); return (GetValueDelegate)dm.CreateDelegate(typeof(GetValueDelegate)); } private static void EmitCastToReference(ILGenerator il, Type type) { if (type.IsValueType) { il.Emit(OpCodes.Unbox_Any, type); } else { il.Emit(OpCodes.Castclass, type); } } }
二、测试代码:
#define SETTER //#define GETTER using System; using System.Diagnostics; using System.Reflection; namespace PublishConsoleApp { internal class Program { private static void Main(string[] args) { // 输出当前运行时 Console.WriteLine(System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion()); // 通过最顶端的预编译符号(SETTER/GETTER)控制测试的代码块 // 调用100w次时间比较 int count = 100_0000; // 测试次数 int testTimes = 5; OrderInfo testObj = new OrderInfo() { OrderID = 123 }; PropertyInfo propInfo = typeof(OrderInfo).GetProperty("OrderID"); for (int k = 0; k < testTimes; k++) { #if SETTER Stopwatch watch1 = Stopwatch.StartNew(); Console.WriteLine($"------------- 设置器测试 ------------- "); for (int i = 0; i < count; i++) { testObj.OrderID = 1; } watch1.Stop(); Console.WriteLine($"直接设置花费时间: {watch1.Elapsed.TotalMilliseconds} 毫秒"); //////////////////////////////////////////////////// var setter = propInfo.GetSetter(); Stopwatch watch2 = Stopwatch.StartNew(); for (int i = 0; i < count; i++) { setter(testObj, 2); } watch2.Stop(); Console.WriteLine($"EmitSet设置花费时间: {watch2.Elapsed.TotalMilliseconds} 毫秒"); //////////////////////////////////////////////////// Stopwatch watch3 = Stopwatch.StartNew(); for (int i = 0; i < count; i++) { propInfo.SetValue(testObj, 3, null); } watch3.Stop(); Console.WriteLine($"纯反射设置花费时间: {watch3.Elapsed.TotalMilliseconds} 毫秒"); //////////////////////////////////////////////////// Console.WriteLine("-------------------"); // 设置器 Console.WriteLine("纯反射/直接设置:{0} / {1} = {2}", watch3.Elapsed.TotalMilliseconds.ToString(), watch1.Elapsed.TotalMilliseconds.ToString(), watch3.Elapsed.TotalMilliseconds / watch1.Elapsed.TotalMilliseconds); Console.WriteLine("纯反射/EmitSet:{0} / {1} = {2}", watch3.Elapsed.TotalMilliseconds.ToString(), watch2.Elapsed.TotalMilliseconds.ToString(), watch3.Elapsed.TotalMilliseconds / watch2.Elapsed.TotalMilliseconds); Console.WriteLine("EmitSet/直接设置:{0} / {1} = {2}", watch2.Elapsed.TotalMilliseconds.ToString(), watch1.Elapsed.TotalMilliseconds.ToString(), watch2.Elapsed.TotalMilliseconds / watch1.Elapsed.TotalMilliseconds); #endif #if GETTER Console.WriteLine($"------------- 访问器测试 ------------- "); Stopwatch watch4 = Stopwatch.StartNew(); for (int i = 0; i < count; i++) { var orderId = testObj.OrderID; } watch4.Stop(); Console.WriteLine($"直接访问花费时间: {watch4.Elapsed.TotalMilliseconds} 毫秒"); //////////////////////////////////////////////////// testObj.OrderID = 4; var getter = propInfo.GetGetter(); Stopwatch watch5 = Stopwatch.StartNew(); for (int i = 0; i < count; i++) { var orderId = getter(testObj); } watch5.Stop(); Console.WriteLine($"EmitSet访问花费时间: {watch5.Elapsed.TotalMilliseconds} 毫秒"); //////////////////////////////////////////////////// testObj.OrderID = 5; Stopwatch watch6 = Stopwatch.StartNew(); for (int i = 0; i < count; i++) { var orderId = propInfo.GetValue(testObj); } watch6.Stop(); Console.WriteLine($"纯反射访问花费时间: {watch6.Elapsed.TotalMilliseconds} 毫秒"); //////////////////////////////////////////////////// Console.WriteLine("-------------------"); // 访问器 Console.WriteLine("纯反射/直接设置:{0} / {1} = {2}", watch6.Elapsed.TotalMilliseconds.ToString(), watch4.Elapsed.TotalMilliseconds.ToString(), watch6.Elapsed.TotalMilliseconds / watch4.Elapsed.TotalMilliseconds); Console.WriteLine("纯反射/EmitSet:{0} / {1} = {2}", watch6.Elapsed.TotalMilliseconds.ToString(), watch5.Elapsed.TotalMilliseconds.ToString(), watch6.Elapsed.TotalMilliseconds / watch5.Elapsed.TotalMilliseconds); Console.WriteLine("EmitSet/直接设置:{0} / {1} = {2}", watch5.Elapsed.TotalMilliseconds.ToString(), watch4.Elapsed.TotalMilliseconds.ToString(), watch5.Elapsed.TotalMilliseconds / watch4.Elapsed.TotalMilliseconds); #endif } //Console.WriteLine("Hello World!"); Console.ReadKey(); } } public class OrderInfo { public int OrderID { get; set; } } }
测试结果: