Reflection-使用反射執行方法的7種方式
最近因為專案需求,努力寫著反射(Reflection)程式,還好哥早在2011年就打過底,但大型實務應用開發之後在 fb 上發表些小心得:
- 取得物件 System.Type 後基本上就無敵了。
- 你能做的、你不能做的,Reflection 都能做。白話:一些簡單的 private 之類限制對 Reflection 無效。
- 製作沒有規範 Assembly 的 Reflection 解析執行程式需要:細心、耐心與草x馬的愛心。(突然覺得那些反編譯器好偉大)
- 接上題,規範好 Assembly 介面定義,大家都會很開心
- 寫法不同,效果不同。純反射 4.1 > dynamic 1.4 < delegate 1.2。delegate 最快。
Expression 網路上的資料它也比 dynamic 快,但我的實作不適合驗證,沒取得數值。- ps. 美國微軟工程師的 Bruce 說,dynamic 能不用就不用。我自己的實作心得:dynamic 沒有比 delegate 快,又有些缺點,我也不建議使用。
後來,實在手癢的受不了,把底層用 Expression 改寫,得到以下結果:
- v1, reflection - 純反射,最慢;
- v2, delegate - 比純反射快 4 倍(自己測);
- v3, expression - 與 delegate 差不多;
速度與可讀性而言,目前 delegate 最好。寫法不同,效果不同。
Reflection - Method Invoke 7 Ways
這裡我將手邊知道的「執行方法」的方法做了以下整理,總共有 7 種方向。
/// <summary> /// 受測類別 /// </summary> public class MyClass { /// <summary> /// Foo 受測方法 /// </summary> /// <param name="value">int 型別值</param> /// <returns></returns> public string Foo(int value) { return $"Foo value: {value}"; } /// <summary> /// Bar 受測方法 /// </summary> /// <param name="value">string 型別值</param> /// <returns></returns> public string Bar(string value) { return $"Bar value: {value}"; } }
Way1:物件直接進行方法呼叫。
這應該是大多數人天天都在使用的方式。把物件 new
起來,直接呼叫。
/// <summary> /// Way1:物件直接進行方法呼叫。 /// </summary> /// <param name="targetObject">The target object.</param> internal void Way1_DirectMethodCall(object targetObject) { var baseName = MethodBase.GetCurrentMethod().Name; var myClass = ((MyClass)targetObject); var fooResult = myClass.Foo(1); var barResult = myClass.Bar("Bruce"); PrintResult(baseName, fooResult, barResult); } [Conditional("DEBUG")] private void PrintResult(string baseName, string foo, string bar) { Console.WriteLine($"{baseName}: {foo}, {bar}"); }
Way2:建立 lambda 並進行方法呼叫。
透過 Action<> 或 Func<> 建立委派,然後進行呼叫。
/// <summary> /// Way2:建立 lambda 並進行方法呼叫。 /// </summary> /// <param name="targetObject">The target object.</param> internal void Way2_CreateALambdaCall(object targetObject) { var baseName = MethodBase.GetCurrentMethod().Name; Func<int, string> fooFunc = f => ((MyClass)targetObject).Foo(f); var fooResult = fooFunc(2); Func<string, string> barFunc = b => ((MyClass)targetObject).Bar(b); var barResult = barFunc("Sherry"); PrintResult(baseName, fooResult, barResult); }
Way3:使用純反射(反映)的 Method.Invoke 進行方法呼叫
使用 Reflection API 的 Method.Invoke 來進行方法呼叫,這也是反射最入門的使用方式。
/// <summary> /// Way3:使用純反射(反映)的 Method.Invoke 進行方法呼叫 /// </summary> /// <param name="targetObject">The target object.</param> internal void Way3_UsingMethodInvoke(object targetObject) { var baseName = MethodBase.GetCurrentMethod().Name; var foo = targetObject.GetType().GetMethod("Foo"); var fooResult = foo.Invoke(targetObject, new object[] { 3 }) as string; var bar = targetObject.GetType().GetMethod("Bar"); var barResult = bar.Invoke(targetObject, new object[] { "Happy" }) as string; PrintResult(baseName, fooResult, barResult); }
Way4:使用 dynamic 關鍵字。
dynamic 缺點很明顯,就是失去強型別效果,但速度不差,也因為失去強型別,撰寫程式碼時不會有 IntelliSense 提示,例如,.Foo(4)
、.Bar("Love")
。
/// <summary> /// Way4:使用 dynamic 關鍵字。 /// </summary> /// <param name="targetObject">The target object.</param> internal void Way4_Using_dynamic_Keyword(object targetObject) { var baseName = MethodBase.GetCurrentMethod().Name; var dynamicTarget = (dynamic)targetObject; var fooResult = dynamicTarget.Foo(4) as string; var barResult = dynamicTarget.Bar("Love") as string; PrintResult(baseName, fooResult, barResult); }
Way5:使用 Delegate.CreateDelegate 建立委派並呼叫方法。
結合 Action<> 或 Func<> 與 Delegate.CreateDelegate 建立委派並執行方法。
/// <summary> /// Way5:使用 Delegate.CreateDelegate 建立委派並呼叫方法。 /// </summary> /// <param name="targetObject">The target object.</param> internal void Way5_CreateDelegateCall(object targetObject) { var baseName = MethodBase.GetCurrentMethod().Name; var fooMethod = targetObject.GetType().GetMethod("Foo"); var fooFunc = (Func<int, string>)Delegate.CreateDelegate(typeof(Func<int, string>), targetObject, fooMethod); var fooResult = fooFunc(5); var barMethod = targetObject.GetType().GetMethod("Bar"); var barFunc = (Func<string, string>)Delegate.CreateDelegate(typeof(Func<string, string>), targetObject, barMethod); var barResult = barFunc("Gina"); PrintResult(baseName, fooResult, barResult); }
Way6: 建立 Expression 並進行方法呼叫。
使用 Expression 建立運算式並呼叫方法。
/// <summary> /// Way6: 建立 expression 並進行方法呼叫。 /// </summary> /// <param name="targetObject">The target object.</param> internal void Way6_CreateExpressionCall(object targetObject) { var baseName = MethodBase.GetCurrentMethod().Name; var thisObject = Expression.Constant(targetObject); var fooMethod = targetObject.GetType().GetMethod("Foo"); var intValue = Expression.Parameter(typeof(int), "value"); var fooCall = Expression.Call(thisObject, fooMethod, intValue); var fooLambda = Expression.Lambda<Func<int, string>>(fooCall, intValue); var fooFunc = fooLambda.Compile(); var fooResult = fooFunc(6); var barMethod = targetObject.GetType().GetMethod("Bar"); var strValue = Expression.Parameter(typeof(string), "value"); var barCall = Expression.Call(thisObject, barMethod, strValue); var barLambda = Expression.Lambda<Func<string, string>>(barCall, strValue); var barFunc = barLambda.Compile(); var barResult = barFunc("Metilda"); PrintResult(baseName, fooResult, barResult); }
Way7:使用 DynamicMethod 動態產生 IL Code
這大概是所有反射中最難寫與最沒可讀性的,但也是功能最強大的 API。
/// <summary> /// Way7: 使用 Reflection.Emit.DynamicMethod 動態產生 IL 並透過 CreateDelegate 建立 Func 委派執行方法。 /// </summary> /// <param name="targetObject">The target object.</param> internal void Way7_UsingDynamicMethodCall(object targetObject) { var baseName = MethodBase.GetCurrentMethod().Name; var type = targetObject.GetType(); var fooMethod = type.GetMethod("Foo"); var fooDynamicMethod = new DynamicMethod("Foo_", typeof(string), new[] { type, typeof(int) }, true); var fooIl = fooDynamicMethod.GetILGenerator(); fooIl.DeclareLocal(typeof(string)); fooIl.Emit(OpCodes.Ldarg_0); fooIl.Emit(OpCodes.Ldarg_1); fooIl.Emit(OpCodes.Call, fooMethod); fooIl.Emit(OpCodes.Ret); var fooFunc = (Func<int, string>)fooDynamicMethod.CreateDelegate(typeof(Func<int, string>), targetObject); var fooResult = fooFunc(7); var barMethod = type.GetMethod("Bar"); var barDynamicMethod = new DynamicMethod("Bar_", typeof(string), new[] { type, typeof(string) }, true); var barIl = barDynamicMethod.GetILGenerator(); barIl.DeclareLocal(typeof(string)); barIl.Emit(OpCodes.Ldarg_0); barIl.Emit(OpCodes.Ldarg_1); barIl.Emit(OpCodes.Call, barMethod); barIl.Emit(OpCodes.Ret); var barFunc = (Func<string, string>)barDynamicMethod.CreateDelegate(typeof(Func<string, string>), targetObject); var barResult = barFunc("Nancy"); PrintResult(baseName, fooResult, barResult); }
小結
瞭解反射的運作的好處是,更加明白物件在底層運作的流程作業。但好好的沒事搞那麼多種做什麼,無聊的人(指我)就會開始寫個程式開始測時間,後來發現,我錯了,「寫法不同,效果不同。」(第三次)有其意義,而意義絕不是寫個for迴圈那麼簡單。
請問WAY6: 建立 EXPRESSION 並進行方法呼叫 如果是 void 沒有回傳資料 要怎麼修改呢? 謝謝
回覆刪除沒有回傳值,不是Action<>嗎!
刪除謝謝
刪除我的method name; type 都是動態的(from string), 所以最後只能用DynamicInvoke , 這有點慢, 請問有解法嗎? 謝謝
刪除var methodContent = JsonConvert.DeserializeObject(msg.CMD);
var serviceType = Type.GetType(Type.GetType(methodContent.ServiceTypeName).AssemblyQualifiedName);
object service = serviceProvider.GetRequiredService(serviceType);
var fooMethod = service.GetType().GetMethod(methodContent.MethodName);
if (methodContent.IsGenericMethod)
{
var genType = Type.GetType(Type.GetType(methodContent.MethodGeneircTypeName).AssemblyQualifiedName);
fooMethod = fooMethod.MakeGenericMethod(genType);
}
var inputType = Type.GetType(Type.GetType(methodContent.MethodInputParamTypeName).AssemblyQualifiedName);
Type delegateType;
Type outputType;
if (methodContent.isVoid)
{
delegateType = typeof(Action<>).MakeGenericType(inputType);
}
else
{
outputType = Type.GetType(Type.GetType(methodContent.MethodOutputParamTypeName).AssemblyQualifiedName);
delegateType = typeof(Func<,>).MakeGenericType(inputType, outputType);
}
var fooFunc = Delegate.CreateDelegate(delegateType, service, fooMethod);
var output = fooFunc.DynamicInvoke(JsonConvert.DeserializeObject(msg.BDY, inputType));