反射调用方法的详细解释
让我一步步拆解这段代码,帮你理解反射是如何调用方法的。
一、普通调用 vs 反射调用
普通调用(编译时确定)
TestA test = new TestA();
test.TestAMethod("world"); // 直接调用,编译时就确定了要调用哪个方法
反射调用(运行时确定)
// 1. 获取类型信息
Type type = typeof(TestA);
// 2. 获取方法信息(通过方法名查找)
MethodInfo method = type.GetMethod("TestAMethod");
// 3. 创建实例(如果没有实例)
TestA test = new TestA();
// 4. 调用方法
method.Invoke(test, new object[] { "world" });
二、逐行拆解
第1行:typeof(TestA)
Type type = typeof(TestA);
// type 变量现在存储了 TestA 类的"元数据信息"
// 包括:类名、方法列表、属性列表、参数信息等
第2行:GetMethod("TestAMethod")
MethodInfo method = type.GetMethod("TestAMethod");
// method 变量存储了 TestAMethod 方法的元数据
// 包括:方法名、参数类型、返回类型、访问修饰符等
// 等价于你手动写:
// method = TestA类中的TestAMethod方法的信息
第3行:method.Invoke(test, new object[] { "world" })
// Invoke 的意思是"调用"
// 参数1:test - 在哪个对象上调用方法
// 参数2:new object[] { "world" } - 传给方法的参数
method.Invoke(test, new object[] { "world" });
// 等价于:test.TestAMethod("world")
三、完整的对比示例
public class TestA
{
public void TestAMethod(string param)
{
Console.WriteLine($"调用了TestAMethod,参数是:{param}");
}
}
class Program
{
static void Main()
{
// ========== 方式1:普通调用 ==========
TestA test1 = new TestA();
test1.TestAMethod("hello");
// 输出:调用了TestAMethod,参数是:hello
// ========== 方式2:反射调用 ==========
// 步骤1:获取类型
Type type = typeof(TestA);
// 步骤2:获取方法(通过方法名字符串)
MethodInfo method = type.GetMethod("TestAMethod");
// 步骤3:创建实例
TestA test2 = new TestA();
// 步骤4:调用方法(参数需要包装成object数组)
method.Invoke(test2, new object[] { "world" });
// 输出:调用了TestAMethod,参数是:world
// 步骤5:也可以先创建实例再调用
object obj = Activator.CreateInstance(type); // 等价于 new TestA()
method.Invoke(obj, new object[] { "reflection" });
// 输出:调用了TestAMethod,参数是:reflection
}
}
四、为什么要用反射?
场景1:运行时才知道方法名
// 假设用户输入方法名
string userInputMethodName = Console.ReadLine(); // 用户输入 "TestAMethod"
// 普通调用做不到,因为编译时不知道用户会输入什么
// 反射可以实现
Type type = typeof(TestA);
MethodInfo method = type.GetMethod(userInputMethodName);
method.Invoke(test, new object[] { "参数" });
场景2:遍历所有方法并调用
// 获取类的所有方法
MethodInfo[] methods = typeof(TestA).GetMethods();
foreach (MethodInfo method in methods)
{
if (method.Name.StartsWith("Test"))
{
// 动态调用所有以Test开头的方法
method.Invoke(test, new object[] { "自动调用" });
}
}
场景3:不知道具体的类
// 通过程序集动态加载类
Assembly assembly = Assembly.Load("MyAssembly");
Type type = assembly.GetType("Base4.特性.TestA");
object obj = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod("TestAMethod");
method.Invoke(obj, new object[] { "动态加载" });
五、Invoke 的参数详解
method.Invoke(test, new object[] { "world" });
// ↑ ↑
// | └── 参数数组(按方法参数顺序)
// └── 实例对象(调用哪个对象的方法)
// 多个参数的情况
public void MethodWithMultipleParams(int id, string name, bool flag)
{
// 方法体
}
// 反射调用
method.Invoke(obj, new object[] { 123, "张三", true });
// ↑ ↑ ↑
// id name flag
六、常见错误和注意事项
错误1:静态方法调用
public class TestA
{
public static void StaticMethod()
{
Console.WriteLine("静态方法");
}
}
// 静态方法不需要实例
MethodInfo method = typeof(TestA).GetMethod("StaticMethod");
method.Invoke(null, null); // 第一个参数传 null
错误2:私有方法调用
public class TestA
{
private void PrivateMethod() { }
}
// 需要指定 BindingFlags
MethodInfo method = typeof(TestA).GetMethod("PrivateMethod",
BindingFlags.NonPublic | BindingFlags.Instance);
method.Invoke(test, null); // 可以调用私有方法
错误3:参数类型不匹配
// 错误:参数类型不匹配
method.Invoke(test, new object[] { 123 }); // 需要string,传了int → 报错
// 正确:参数类型要匹配
method.Invoke(test, new object[] { "123" }); // 传string
七、性能对比
// 普通调用:快
test.TestAMethod("hello");
// 反射调用:慢(约10-100倍)
method.Invoke(test, new object[] { "hello" });
// 如果频繁调用,可以缓存MethodInfo
private static MethodInfo _cachedMethod = typeof(TestA).GetMethod("TestAMethod");
// 多次使用缓存的方法
_cachedMethod.Invoke(test, new object[] { "hello" });
八、实际应用示例
// 一个简单的插件系统
public interface IPlugin
{
void Execute(string data);
}
public class PluginA : IPlugin
{
public void Execute(string data) => Console.WriteLine($"PluginA: {data}");
}
public class PluginB : IPlugin
{
public void Execute(string data) => Console.WriteLine($"PluginB: {data}");
}
class Program
{
static void Main()
{
string pluginName = Console.ReadLine(); // 用户输入 "PluginA"
// 动态加载插件
Type type = Type.GetType($"MyNamespace.{pluginName}");
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
// 调用插件方法(这里用接口调用,不用反射也行)
// 但如果插件没有实现接口,就必须用反射
MethodInfo method = type.GetMethod("Execute");
method.Invoke(plugin, new object[] { "用户数据" });
}
}
总结
method.Invoke(test, new object[] { "world" });
// 就相当于:test.TestAMethod("world")
// 区别是:
// - 普通调用:编译时确定,直接调用
// - 反射调用:运行时查找,通过字符串名字调用
// 记忆口诀:
// typeof(类) → 获取类信息
// GetMethod(方法名) → 获取方法信息
// Invoke(实例, 参数数组) → 执行方法
反射的核心思想是:把"调用方法"这个操作,从编译时推迟到运行时,通过方法名的字符串来实现动态调用。