傳統反射以字串指定成員名稱,重構時易導致執行期錯誤。LINQ 運算式樹(Expression<TDelegate>)可在編譯期檢查成員存取,實現強型別反射。
public static class MyReflection {
public delegate void Operation<T>(T declaringType);
public delegate void Operation<T, A1, A2>(T declaringType, object a1, object a2);
public delegate object OperationWithReturnValue<T>(T declaringType);
public static MethodInfo Method<T>(Expression<Operation<T>> method) => GetMethodInfo(method);
public static MethodInfo Method<T>(Expression<OperationWithReturnValue<T>> method) => GetMethodInfo(method);
public static MethodInfo Method<T, A1, A2>(Expression<Operation<T, A1, A2>> method) => GetMethodInfo(method);
private static MethodInfo GetMethodInfo(Expression method) {
var lambda = method as LambdaExpression;
if (lambda == null) throw new ArgumentNullException();
MethodCallExpression callExpr = null;
if (lambda.Body.NodeType == ExpressionType.Call)
callExpr = lambda.Body as MethodCallExpression;
else if (lambda.Body.NodeType == ExpressionType.Convert)
callExpr = ((UnaryExpression)lambda.Body).Operand as MethodCallExpression;
if (callExpr == null) throw new ArgumentException();
return callExpr.Method;
}
}
ExpressionType.Call 對應無傳回值方法,ExpressionType.Convert 對應有傳回值方法(因 Lambda 轉換為 Func 需轉型)。
public class MyTest {
public void TestFunc() => Console.WriteLine("Test Function");
public static void StaticTestFunc() => Console.WriteLine("Test Static Function");
public int TestFuncWithReturnValue() { Console.WriteLine("Test function with return value"); return 100; }
public void TestFuncWithArgs(string arg1, int arg2) => Console.WriteLine($"Test function with arguments [{arg1},{arg2}]");
}
// 使用方式
var mi = MyReflection.Method<MyTest>(m => m.TestFunc());
mi.Invoke(new MyTest(), null);
mi = MyReflection.Method<MyTest>(m => MyTest.StaticTestFunc());
mi.Invoke(null, null);
mi = MyReflection.Method<MyTest>(m => m.TestFuncWithReturnValue());
Console.WriteLine("Return value: {0}", mi.Invoke(new MyTest(), null));
mi = MyReflection.Method<MyTest, object, object>((m, x, y) => m.TestFuncWithArgs((string)x, (int)y));
mi.Invoke(new MyTest(), new object[] { "Argument1", 50 });