在频繁的通过反射来设置和获取属性的值时是比较耗时的,本章通过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; }
}
}
测试结果:

