今天在工作上遇到這麼個需求:需要擷取對象上所有屬性的值,但並事先並不知道對象的類型。 我的第一反應就是使用反射,但是這個操作會進行多次,大量的反射肯定會有效能影響。雖然對我這個項目無關緊要,但我還是選擇了另外一種解決方案:構建運算式樹狀架構,再產生委託,然後將委託緩衝在字典裡。代碼如下:
首先構建運算式樹狀架構(類似這種形式:'(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倍。
看來在我這種情況下使用字典緩衝委託的效果並不是太好。不知道是否有更好的方法來緩衝委託。