Emit优化反射(属性的设置与获取)

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

 

测试结果:

Emit优化反射(属性的设置与获取)

 

Emit优化反射(属性的设置与获取)

 

上一篇:C# windows服务启动时报错服务没有及时响应启动或者控制请求,错误代码1053


下一篇:clickhouse 类型错误、聚合函数错误——字段别名的注意事项