今天在工做上遇到這麼個需求:須要獲取對象上全部屬性的值,但並事先並不知道對象的類型。 個人第一反應就是使用反射,可是這個操做會進行屢次,大量的反射確定會有性能影響。雖然對我這個項目可有可無,但我仍是選擇了另一種解決方案:構建表達式樹,再生成委託,而後將委託緩存在字典裏。代碼以下: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。
測試結果:
能夠看到,在千萬次的狀況下(十萬,百萬也是如此),這個版本效率比表達式樹緩存在字典裏的效率還要高一些。
最後,若是個人代碼有錯誤或者測試方法不對,歡迎你們指出