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));
        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。

    测试结果:

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

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

  • 相关阅读:
    第十四周 Leetcode 315. Count of Smaller Numbers After Self(HARD) 主席树
    POJ1050 To the Max 最大子矩阵
    POJ1259 The Picnic 最大空凸包问题 DP
    POJ 3734 Blocks 矩阵递推
    POJ2686 Traveling by Stagecoach 状态压缩DP
    iOS上架ipa上传问题那些事
    深入浅出iOS事件机制
    iOS如何跳到系统设置里的各种设置界面
    坑爹的私有API
    业务层网络请求封装
  • 原文地址:https://www.cnblogs.com/hao-dotnet/p/4181283.html
Copyright © 2011-2022 走看看