今天在工做上遇到这么个需求:须要获取对象上全部属性的值,但并事先并不知道对象的类型。 个人第一反应就是使用反射,可是这个操做会进行屡次,大量的反射确定会有性能影响。虽然对我这个项目可有可无,但我仍是选择了另一种解决方案:构建表达式树,再生成委托,而后将委托缓存在字典里。代码以下:express
首先构建表达式树(相似这种形式:'(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); }
接下来是二者的测试代码:ui
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);
在个人预想之中是这样的:表达式树版本在调用次数不多的状况下会慢于反射版本,随着次数增多,表达式树版本的优点会愈来愈明显。spa
测试结果:pwa
在调用次数为十万、百万、千万次的状况下,表达式书版本的优点随着次数而提升。code
PS:以前在代码中犯了一些错误致使表达式树版本的效率比反射还低,还把缘由归结于Dictionary的效率,确实不应。可是为什么这些小错误会致使如此的差距我还没弄明白,搞明白以后再写一篇博客吧。对象
更新:blog
通过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。
测试结果:
能够看到,在千万次的状况下(十万,百万也是如此),这个版本效率比表达式树缓存在字典里的效率还要高一些。
最后,若是个人代码有错误或者测试方法不对,欢迎你们指出