zoukankan      html  css  js  c++  java
  • 使用表达式树和反射来访问对象属性的性能比较

    今天在工作上遇到这么个需求:需要获取对象上所有属性的值,但并事先并不知道对象的类型。 我的第一反应就是使用反射,但是这个操作会进行多次,大量的反射肯定会有性能影响。虽然对我这个项目无关紧要,但我还是选择了另外一种解决方案:构建表达式树,再生成委托,然后将委托缓存在字典里。代码如下:

    首先构建表达式树(类似这种形式:'(a) => a.xx'),并生成委托:

    复制代码
    private static Func<TObject, object> BuildDynamicGetPropertyValueDelegate<TObject>(PropertyInfo property)
    {
        var instanceExpression = Expression.Parameter(property.ReflectedType, "instance");
        var memberExpression = Expression.Property(instanceExpression, property);
        var convertExpression = Expression.Convert(memberExpression, typeof(object));  //生成的Body就是a.xx; xx代表属性名
        var lambdaExpression = Expression.Lambda<Func<TObject, object>>(convertExpression, instanceExpression);
        return lambdaExpression.Compile();
    }
    复制代码

    接着,当需要获取属性的值时,先在字典里查看是否有已经生成好的委托,有的话取出委托执行获取属性值。没有则构建表达式树生成委托,并放入字典中:

    复制代码
    private static Dictionary<PropertyInfo, Delegate> delegateCache = new Dictionary<PropertyInfo, Delegate>();
    
    public static object GetPropertyValueUseExpression<TObject>(TObject obj, PropertyInfo property)
    {
        Delegate d;
        if (delegateCache.TryGetValue(property, out d))
        {
            var func = (Func<TObject, object>)d;
            return func(obj);
        }
    
        var getValueDelegate = BuildDynamicGetPropertyValueDelegate<TObject>(property);
        delegateCache[property] = getValueDelegate;
        return getValueDelegate(obj);
    }
    复制代码

    就这么简单,完成之后,我想测试一下表达式树版本和反射版本的性能差距如何,于是我又简单实现反射版本作为测试对比:

    public static object GetPropertyValueUseReflection<TObject>(TObject obj, PropertyInfo propertyInfo)
    {
        return propertyInfo.GetValue(obj);
    }

    接下来是两者的测试代码:

    复制代码
    class Car 
    {
        public string Make { get; set; }
        public string Model { get; set; }
        public int Capacity { get; set; }
    }
    
    .....
    
    int repeatTimes = 10000;
    PropertyInfo property = typeof(Car).GetProperty("Make");
    Car car = new Car();
    
    Stopwatch stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < repeatTimes; i++)
    {
        GetPropertyValueUseExpression(car, property);
    }
    stopwatch.Stop();
    Console.WriteLine("Repeated {0}, Cache in Dictionary expression used time: {1} ms", repeatTimes, stopwatch.ElapsedTicks);
    
    stopwatch.Reset();
    stopwatch.Start();
    for (int i = 0; i < repeatTimes; i++)
    {
        GetPropertyValueUseReflection(car, property);
    }
    stopwatch.Stop();
    Console.WriteLine("Repeated {0}, reflection used time: {1} ms", repeatTimes, stopwatch.ElapsedTicks);
    复制代码

    在我的预想之中是这样的:表达式树版本在调用次数很少的情况下会慢于反射版本,随着次数增多,表达式树版本的优势会越来越明显。

    测试结果:

    在调用次数为十万、百万、千万次的情况下,表达式书版本的优势随着次数而提高。

    PS:之前在代码中犯了一些错误导致表达式树版本的效率比反射还低,还把原因归结于Dictionary的效率,确实不该。但是为何这些小错误会导致如此的差距我还没弄明白,搞明白之后再写一篇博客吧。

    更新:

    经过Echofool、zhaxg两位园友的提示,其实访问属性的委托可以不用放在字典里,而是通过多接收一个参数再根据switch case来获取相应的属性值,代码如下:

    复制代码
    public class PropertyDynamicGetter<T>
    {
        private static Func<T, string, object> cachedGetDelegate;
    
        public PropertyDynamicGetter()
        {
            if (cachedGetDelegate == null)
            {
                var properties = typeof(T).GetProperties();
                cachedGetDelegate = BuildDynamicGetDelegate(properties);
            }
        }
    
        public object Execute(T obj, string propertyName)
        {
            return cachedGetDelegate(obj, propertyName);
        }
    
        private Func<T, string, object> BuildDynamicGetDelegate(PropertyInfo[] properties)
        {
            var objParamExpression = Expression.Parameter(typeof(T), "obj");
            var nameParamExpression = Expression.Parameter(typeof(string), "name");
            var variableExpression = Expression.Variable(typeof(object), "propertyValue");
    
            List<SwitchCase> switchCases = new List<SwitchCase>();
            foreach (var property in properties)
            {
                var getPropertyExpression = Expression.Property(objParamExpression, property);
                var convertPropertyExpression = Expression.Convert(getPropertyExpression, typeof(object));
                var assignExpression = Expression.Assign(variableExpression, convertPropertyExpression);
                var switchCase = Expression.SwitchCase(assignExpression, Expression.Constant(property.Name));
                switchCases.Add(switchCase);
            }
    
            //set null when default
            var defaultBodyExpression = Expression.Assign(variableExpression, Expression.Constant(null));
            var switchExpression = Expression.Switch(nameParamExpression, defaultBodyExpression, switchCases.ToArray());
            var blockExpression = Expression.Block(typeof(object), new[] { variableExpression }, switchExpression);
            var lambdaExpression = Expression.Lambda<Func<T, string, object>>(blockExpression, objParamExpression, nameParamExpression);
            return lambdaExpression.Compile();
        }
    }
    复制代码

    这个版本不使用字典,从而去除了从字典取对象的影响。它实现上先是取出对象所有的属性,然后在构建表达式树时根据属性名使用Switch。

    测试结果:

    可以看到,在千万次的情况下(十万,百万也是如此),这个版本效率比表达式树缓存在字典里的效率还要高一些。

    最后,如果我的代码有错误或者测试方法不对,欢迎大家指出

  • 相关阅读:
    vim插件:显示树形目录插件NERDTree安装 和 使用【转】
    CMake 入门实战【转】
    在 linux 下使用 CMake 构建应用程序【转】
    Buildroot构建指南——根文件系统(Rootfs)【转】
    Buildroot构建指南--快速上手与实用技巧【转】
    Vim升华之树形目录插件NERDTree安装图解【转】
    【转】Android端与Android端利用WIFI进行FTP通信
    【转】Android 服务器之SFTP服务器上传下载功能 -- 不错
    【转】session setup failed: NT_STATUS_LOGON_FAILURE -- 不错
    【转】Mac OS X开机启动Path had bad permissions错误解决方案
  • 原文地址:https://www.cnblogs.com/sjqq/p/8421585.html
Copyright © 2011-2022 走看看