反射调用方法

反射调用方法的详细解释

让我一步步拆解这段代码,帮你理解反射是如何调用方法的。

一、普通调用 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(实例, 参数数组) → 执行方法

反射的核心思想是:把"调用方法"这个操作,从编译时推迟到运行时,通过方法名的字符串来实现动态调用。

相关推荐
unicrom_深圳市由你创科技8 小时前
C# 如何实现对象序列化
开发语言·c#
成都易yisdong9 小时前
实现三北方向转换计算器(集成 WMM2025 地磁模型)
开发语言·windows·算法·c#·visual studio
guygg8810 小时前
OPC UA Helper: 连接PLC获取变量值
服务器·网络·c#
成都易yisdong12 小时前
基于C#和WMM2025模型的地磁参数计算器实现
开发语言·c#
预见AI12 小时前
C#索引器练习题
开发语言·计算机视觉·c#
~plus~13 小时前
C# 内存管理深度剖析:从 Span<T> 到 Memory<T> 再到 ArrayPool
开发语言·c#
cici1587413 小时前
C#与西门子S7-1200通讯实例
开发语言·c#
~plus~13 小时前
C# 异步编程深度剖析:从 async/await 到 ValueTask
开发语言·c#