最近又換了工做。而後開心是之後又能比較頻繁的關注博客園了。辦離職手續的這一個月梳理了下近一年本身寫的東西,而後就有了此文以及附帶的代碼。html
關於反射,竊覺得,他只是比較慢。在這個前提下,我的認爲只有在進行集合操做的時候談論反射的性能纔有意義。同時,只有在這個集合達到必定規模的時候,纔有改進的餘地。好比說,1000個元素的集合。後文會給出詳細的數據。express
借花獻佛:編程
http://www.cnblogs.com/ASPNET2008/archive/2009/05/22/1485888.html緩存
http://www.cnblogs.com/jams742003/archive/2009/12/23/1630432.html編程語言
先給例子,再來探討爲何。這裏是幾個Case:性能
1.根據屬性名提取一個IEnuerable<object>集合元素的屬性/字段的值。測試
2.根據屬性名更新上訴對象的元素的屬性/字段值。this
3.將上訴集合投影爲指定元素類型的集合。編碼
如下是針對這3個Case的實現:spa
1.加載屬性
using System; using System.Collections.Generic; using System.Linq; using System.Caching; using System.Text; using System.Extension; using System.Collections.Concurrent; namespace System.Linq.Expressions { public class PropertyFieldLoader : CacheBlock<string, Func<object, object>> { protected PropertyFieldLoader() { } public object Load(object tObj, Type type, string propertyPath) { return Compile(type, propertyPath)(tObj); } public T Load<T>(object tObj, Type type, string propertyPath) where T : class { return Load(tObj, type, propertyPath) as T; } public Func<object, object> Compile(Type type, string propertyPath) { var key = "Get_" + type.FullName + "_"; var func = this.ConcurrentDic.GetOrAdd(key + "." + propertyPath, (string k) => { ParameterExpression paramInstance = Expression.Parameter(typeof(object), "obj"); Expression expression = Expression.Convert(paramInstance, type); foreach (var pro in propertyPath.Split('.')) expression = Expression.PropertyOrField(expression, pro); expression = Expression.Convert(expression, typeof(object)); var exp = Expression.Lambda<Func<object, object>>(expression, paramInstance); return exp.Compile(); }); return func; } public static PropertyFieldLoader Instance = new PropertyFieldLoader(); } }
2.更新屬性
using System; using System.Collections.Generic; using System.Linq; using System.Caching; using System.Text; using System.Extension; using System.Collections.Concurrent; namespace System.Linq.Expressions { public class PropertyFieldSetter : CacheBlock<string, Action<object, object>> { protected PropertyFieldSetter() { } public void Set(object obj, string propertyPath, object value) { var tObj = obj.GetType(); var tValue = value.GetType(); var act = Compile(tObj, tValue, propertyPath); act(obj, value); } public static PropertyFieldSetter Instance = new PropertyFieldSetter(); public Action<object, object> Compile(Type typeObj,Type typeValue, string propertyPath) { var key = "Set_" + typeObj.FullName + "_" + typeValue.FullName; var act = ConcurrentDic.GetOrAdd(key + "." + propertyPath, (s) => { ParameterExpression paramInstance = Expression.Parameter(typeof(object), "obj"); ParameterExpression paramValue = Expression.Parameter(typeof(object), "value"); Expression expression = Expression.Convert(paramInstance, typeObj); foreach (var pro in propertyPath.Split('.')) expression = Expression.PropertyOrField(expression, pro); var value = Expression.Convert(paramValue, typeValue); expression = Expression.Assign(expression, value); var exp = Expression.Lambda<Action<object, object>>(expression, paramInstance, paramValue); return exp.Compile(); }); return act; } } }
3.數據轉換
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Caching; namespace System.Linq.Expressions { public class DataTransfer<T> : CacheBlock<string, Func<object, T>> where T : new() { protected DataTransfer() { } public Func<object, T> Compile(Type inType) { var outType = typeof(T); var pKey = "Transfer_" + inType.FullName + "_" + outType.FullName; var func = this.ConcurrentDic.GetOrAdd(pKey, (string ckey) => { var proOrFie = outType.GetProperties().Cast<MemberInfo>() .Union(outType.GetFields().Cast<MemberInfo>()) .Union(inType.GetProperties().Cast<MemberInfo>()) .Union(inType.GetFields().Cast<MemberInfo>()) .GroupBy(i => i.Name).Where(i => i.Count() == 2) .ToDictionary(i => i.Key, i => i); var returnTarget = Expression.Label(outType); var returnLabel = Expression.Label(returnTarget, Expression.Default(outType)); var pramSource = Expression.Parameter(typeof(object)); var parmConverted = Expression.Convert(pramSource, inType); var newExp = Expression.New(outType); var variate = Expression.Parameter(outType, "instance"); var assVar = Expression.Assign(variate, newExp); Expression[] expressions = new Expression[proOrFie.Count + 3]; expressions[0] = assVar; var assExps = proOrFie.Keys.Select(i => { var value = Expression.PropertyOrField(parmConverted, i); var prop = Expression.PropertyOrField(variate, i); var assIgnExp = Expression.Assign(prop, value); return assIgnExp; }); var index = 1; foreach (var exp in assExps) { expressions[index] = exp; index++; } expressions[index] = Expression.Return(returnTarget,variate); expressions[index + 1] = returnLabel; var block = Expression.Block(new[] { variate }, expressions); var expression = Expression.Lambda<Func<object, T>>(block, pramSource); return expression.Compile(); }); return func; } public static DataTransfer<T> Instance = new DataTransfer<T>(); } }
首先,使用ExpressionTree在第一次調用的時候,會產生很是明顯的性能開銷。因此,在進行測試以前,都會對各個方法預調用一次,從而緩存ET(簡寫)的編譯(不帶引號)結果。同事,爲了公平起見,對比的基於反射實現的代碼頁進行了略微的處理。
對比代碼:
1.加載屬性
() => ducks.Select(i => pro.GetValue(i)).ToArray()
2.更新屬性
foreach (var duck in ducks) { pro.SetValue(duck, "AssignedReflection"); }
3.數據轉換
public static Dictionary<string, Tuple<PropertyInfo, PropertyInfo>> Analyze(Type ts, Type to) { var names = to.GetProperties() .Union(ts.GetProperties()) .GroupBy(i => i.Name).Where(i => i.Count() == 2) .Select(i => i.Key); var result = ts.GetProperties() .Where(i => names.Contains(i.Name)).OrderBy(i => i.Name) .Zip(to.GetProperties().Where(i => names.Contains(i.Name)).OrderBy(i => i.Name), (a, b) => new { Key = a.Name, Item1 = a, Item2 = b }) .ToDictionary(i => i.Key, i => new Tuple<PropertyInfo, PropertyInfo>(i.Item1, i.Item2)); return result; } /// <summary> /// Item1:Source,Item2:Target /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="OutT"></typeparam> /// <param name="source"></param> /// <param name="refResult"></param> /// <returns></returns> public static IEnumerable<OutT> TransferByReflection<T, OutT>(IEnumerable<T> source, Dictionary<string, Tuple<PropertyInfo, PropertyInfo>> refResult) where OutT : new() { foreach (var sor in source) { var target = new OutT(); foreach (var p in refResult) { object value = p.Value.Item1.GetValue(sor, null); p.Value.Item2.SetValue(target, value); } yield return target; } }
同時還和對應的硬編碼進行了對比,這裏就不貼代碼了。如下是結果(Tick爲時間單位(1/10000毫秒)):
ExpressionTree | NativeCode | Reflection | ArrayLenth | Benefits | Action |
26 | 12 | 21 | 10 | -5 | AccessProperty |
49 | 24 | 90 | 100 | 41 | AccessProperty |
224 | 140 | 842 | 1000 | 618 | AccessProperty |
2201 | 1294 | 7807 | 10000 | 5606 | AccessProperty |
35884 | 14730 | 77336 | 100000 | 41452 | AccessProperty |
223865 | 138630 | 789335 | 1000000 | 565470 | AccessProperty |
ExpressionTree | NativeCode | Reflection | ArrayLenth | Benefits | Action |
14 | 5 | 17 | 10 | 3 | AssignValue |
23 | 10 | 101 | 100 | 78 | AssignValue |
109 | 45 | 983 | 1000 | 874 | AssignValue |
931 | 410 | 10542 | 10000 | 9611 | AssignValue |
9437 | 3720 | 116147 | 100000 | 106710 | AssignValue |
94895 | 45392 | 1064471 | 1000000 | 969576 | AssignValue |
ExpressionTree | NativeCode | Reflection | ArrayLenth | Benefits | Action |
47 | 11 | 11 | 10 | -36 | TransferData |
101 | 46 | 744 | 100 | 643 | TransferData |
538 | 240 | 7004 | 1000 | 6466 | TransferData |
4945 | 2331 | 77758 | 10000 | 72813 | TransferData |
91831 | 23606 | 785472 | 100000 | 693641 | TransferData |
960657 | 681635 | 8022245 | 1000000 | 7061588 | TransferData |
同時,這裏附上對應的圖表:
由以上圖表可知,當元素小於1000個的時候,收益不會超過1毫秒。因此,此時的改進空間能夠說是幾乎沒有。固然,若是在整個項目中有不少地方,好比說...1000個地方,使用了相似的代碼(反射),確實會致使性能問題。但這已經不是[反射]的錯了。
上訴代碼作的事情其實只有一件:「將邏輯緩存起來。」爲何說是將邏輯緩存起來?這是由於我的以爲,ET實際上是描述代碼邏輯的一種手段,和編程語言差不到哪裏去了。只不過它編譯的時候沒有語法解析這一步。因此和調用編譯器動態編譯相比,「性能更好」也更輕量(臆測)。
而「爲何ET比反射快」,這個問題實際上應該是「爲何反射比ET慢」。反射調用的時候,首先要搜索屬性,而後進行操做的時候又要進行大量的類型轉換和校驗。而對於ET而言,這些操做會在第一次完成以後「固定」並緩存起來。
經過這次探討,咱們收穫了什麼呢?
1.只要當心一點,反射根本不是噩夢,讓那些反射黑閉嘴。
2.就算反射致使了問題,咱們仍然有辦法解決。從以上數據來看,耗時爲硬編碼的兩倍左右。若是2X->∞,那X也好不到哪裏去。
3.好用。經過這種方式,咱們能很是方便的訪問「屬性的屬性」。好比:
ducks.SelectPropertyOrField<Duck, object>("Name.Length")
4.更多可能性。咱們甚至能夠在不產生太大的性能開銷的狀況下完成一個深拷貝組件,來實現必定程度上通用的深拷貝。
這裏附上我使用的代碼,供你們參考。
http://pan.baidu.com/s/1c0dF3FE(OneDrive不給力)