很多人都說使用反射會有效能問題,那到底會比直接呼叫慢多少呢,下面就來測試一下。
下面就來寫個demo來驗證下直接呼叫和反射呼叫的效能差異,程式碼如下:
1 namespace ConsoleApplication7 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 //比较直接调用和反射调用的性能差异 8 //7ms vs 365ms 9 int times = 1000000;10 var program = new Program();11 CodeTimerHelper.Initialize();12 13 CodeTimerHelper.Time("直接调用", times, () =>14 {15 program.Call();16 });17 18 var t = typeof(Program);19 var obj = Activator.CreateInstance(t);20 CodeTimerHelper.Time("反射调用", times, () =>21 {22 t.InvokeMember("Call", BindingFlags.InvokeMethod, null, obj, null);23 });24 25 Console.ReadKey();26 }27 28 /// <summary>29 /// 测试方法30 /// </summary>31 public void Call()32 {33 }34 35 }36 }
測試結果:
從100萬次呼叫結果來看,確實就像很多人所說的,#兩者在性能上具有數量級的差距。
#既然反射性能有損失,那麼具體損失在哪裡?
1,反射是基於組件和元數據的,在使用反射的時候,會搜尋元數據,而元數據是基於字串的,並且無法預先編譯,所以這一系列的操作對性能有影響。
2,大量的裝箱拆箱也對效能有影響。由於我們對目標類型是未知的,而且向方法傳遞的參數通常是object類型的,所以會有大量的裝箱和拆箱。
我們已經知道使用反射有效能問題,但有些場景下又得使用反射技術,所以要想辦法優化反射效能。
這裡就引用老趙公開的System.Linq.Expressions.Expression
1 //3,基于表达式树2 var methodInfo = t.GetMethod("Call");3 var executor = new DynamicMethodExecutor(methodInfo);4 CodeTimerHelper.Time("Dynamic executor", times, () =>5 {6 executor.Execute(obj, null);7 });
#
測試結果:
哇,同樣的100萬次調用,使用DynamicMethodExecutor調用跟直接呼叫的效能相差無幾。
附DynamicMethodExecutor的封裝程式碼:
1 /// <summary> 2 /// 3 /// </summary> 4 public class DynamicMethodExecutor 5 { 6 private Func<object, object[], object> m_execute; 7 8 public DynamicMethodExecutor(MethodInfo methodInfo) 9 {10 this.m_execute = this.GetExecuteDelegate(methodInfo);11 }12 13 public object Execute(object instance, object[] parameters)14 {15 return this.m_execute(instance, parameters);16 }17 18 private Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo)19 {20 // parameters to execute21 ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");22 ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");23 24 // build parameter list25 List<Expression> parameterExpressions = new List<Expression>();26 ParameterInfo[] paramInfos = methodInfo.GetParameters();27 for (int i = 0; i < paramInfos.Length; i++)28 {29 // (Ti)parameters[i]30 BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));31 UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);32 parameterExpressions.Add(valueCast);33 }34 35 // non-instance for static method, or ((TInstance)instance)36 Expression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, methodInfo.ReflectedType);37 38 // static invoke or ((TInstance)instance).Method39 MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);40 41 // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)42 if (methodCall.Type == typeof(void))43 {44 Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(methodCall, instanceParameter, parametersParameter);45 Action<object, object[]> execute = lambda.Compile();46 return (instance, parameters) =>47 {48 execute(instance, parameters);49 return null;50 };51 }52 else53 {54 UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));55 Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(castMethodCall, instanceParameter, parametersParameter);56 return lambda.Compile();57 }58 }
除了使用linq的表達式樹來產生Delegate的方法外,還有例如,CodeDom生成程式碼並動態編譯,或使用Emit來直接寫IL的方法來提高反射的效能,但相對來說,上面這個方法是最簡單的。
至此,整個反射的總結就完成了!
#方法的直接調用,反射調用與…Lambda表達式調用
C#基礎知識梳理系列十五:反射
二、什麼是反射、反射可以做些什麼
以上是直接呼叫vs反射呼叫實例教程的詳細內容。更多資訊請關注PHP中文網其他相關文章!