使用表达式树和反射来访问对象属性的性能比较_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > 使用表达式树和反射来访问对象属性的性能比较

使用表达式树和反射来访问对象属性的性能比较

 2014/12/24 12:49:53  浩GE  程序员俱乐部  我要评论(0)
  • 摘要:今天在工作上遇到这么个需求:需要获取对象上所有属性的值,但并事先并不知道对象的类型。我的第一反应就是使用反射,但是这个操作会进行多次,大量的反射肯定会有性能影响。虽然对我这个项目无关紧要,但我还是选择了另外一种解决方案:构建表达式树,再生成委托,然后将委托缓存在字典里。代码如下:首先构建表达式树(类似这种形式:'(a)=>a.xx'),并生成委托:privatestaticDelegateBuildDynamicGetPropertyValueDelegate
  • 标签:

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

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

private static Delegate BuildDynamicGetPropertyValueDelegate(PropertyInfo property)
{
    var instanceExpression = Expression.Parameter(property.ReflectedType, "instance");
    var memberExpression = Expression.Property(instanceExpression, property);

    var lambdaExpression = Expression.Lambda(memberExpression, instanceExpression);
    return lambdaExpression.Compile();
}

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

private static Dictionary<PropertyInfo, Delegate> delegateCache = new Dictionary<PropertyInfo, Delegate>();

public static object GetPropertyValueUseExpression<TObject>(TObject obj, PropertyInfo property)
{         
    if (delegateCache.ContainsKey(property))
    {
        var func = (Func<TObject, object>)delegateCache[property];
        return func(obj);
    }

    var getValueDelegate = BuildDynamicGetPropertyValueDelegate(property);
    delegateCache[property] = getValueDelegate;
    return ((Func<TObject, object>)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);

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

但是测试结果却出乎我的意料!!!

在调用次数为十万、百万、千万次的情况下,两者所用的时间差不多,而且反射版本居然还要快一些。这可让我郁闷不已。

郁闷之后,我就在想是不是因为字典的原因导致两者性能差不多,就添加了以下测试代码:

stopwatch.Reset();
stopwatch.Start();
var func = (Func<Car, object>)BuildDynamicGetPropertyValueDelegate(property);
for (int i = 0; i < repeatTimes; i++)
{
    func(car);
}
stopwatch.Stop();
Console.WriteLine("Repeated {0}, Immediate call expression used time: {1} ticks", repeatTimes, stopwatch.ElapsedTicks);

这部分测试代码,在构建表达式树生成委托之后,直接调用,去除了字典的影响。测试结果如下:

果不其然,去除字典之后速度快了10倍。

看来在我这种情况下使用字典缓存委托的效果并不是太好。不知道是否有更好的方法来缓存委托。

更新:

经过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 assignExpression = Expression.Assign(variableExpression, getPropertyExpression);
            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();
    }
}

这样,就可以去除字典的影响,而是通过泛型来"缓存"。因为现在用的是不同的机子,这种方法的测试数据就晚上再贴上吧。

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

 

  • 相关文章
发表评论
用户名: 匿名