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));