反射调用的难点在于加载失败、类型找不到、方法签名不匹配三类问题;应优先用Assembly.LoadFile加载插件,配合完整限定名获取类型,静态方法Invoke传null,实例方法确保obj类型严格匹配,高频调用建议缓存Delegate。

反射调用本身不难,难的是加载失败、类型找不到、方法签名不匹配这三类问题占了 90% 的实际报错。
Assembly.LoadFrom 和 Assembly.LoadFile 到底该用哪个
多数人一上来就用 Assembly.LoadFrom,但它会把路径中同名但不同版本的已加载程序集直接复用,导致你“以为加载了新 DLL”,实际调用的还是老版本里的旧方法。
Assembly.LoadFile 更可控:它不走 GAC 或当前上下文,纯按文件路径读取字节流,适合动态插件场景。但注意它不会自动解析依赖——如果目标 DLL 引用了 Newtonsoft.Json.dll,而你没提前加载它,GetType() 就会返回 null。
- 本地调试阶段优先用
Assembly.LoadFile(@"C:\plug\MyPlugin.dll") - 生产环境需依赖管理时,改用
AssemblyLoadContext(.NET Core 3.0+)隔离加载 - 绝对不要用
Assembly.Load("MyPlugin, Version=1.0.0.0")去加载磁盘上的文件——这是从 GAC 或当前上下文找,不是从路径读
Type.GetType() 返回 null?检查全名和程序集限定名
反射里最常踩的坑是传错类型名:Type.GetType("MyPlugin.MyService") 在跨程序集时几乎必返回 null,因为没指定程序集上下文。
正确做法是拼出完整限定名:Type.GetType("MyPlugin.MyService, MyPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")。更稳妥的是先拿到 Assembly 实例,再用 assembly.GetType("MyPlugin.MyService")。
- 类名必须带命名空间,大小写敏感
- 若类型是嵌套类,用加号连接:
OuterClass+InnerClass - 泛型类型要写成
System.Collections.Generic.List`1[[System.String]],反引号 + 数字表示泛型参数个数
MethodInfo.Invoke 报 “Object does not match target type”
这个错误不是方法没找到,而是你传错了 obj 参数——Invoke 的第一个参数是调用实例,如果方法是静态的,必须传 null;如果是实例方法,传的对象类型必须和声明该方法的 Type 完全一致(不能是子类或接口)。
例如你用 typeof(IFoo).GetMethod("DoWork") 拿到方法,却传入一个 FooImpl 实例,就会失败。得用 fooImpl.GetType().GetMethod("DoWork") 或直接在 IFoo 上调用 GetMethod 并确保传入的是 IFoo 类型对象。
- 静态方法:用
method.Invoke(null, args) - 实例方法:确保
obj是该Type的实例,或其确切子类(非接口实现类) - 参数数组里每个元素类型必须和方法签名严格匹配,
int不能传long,string不能传null(除非参数允许)
性能差、异常堆栈难读?考虑用 Delegate.CreateDelegate
反复调用同一个方法时,每次都 GetMethod().Invoke() 开销很大,且异常堆栈里全是 RuntimeMethodHandle.Invoke,看不出真实出错位置。
把 MethodInfo 编译成委托能提速 5–10 倍,并保留原始调用链路:var func = (Func<string int>)Delegate.CreateDelegate(typeof(Func<string int>), obj, method)</string></string>。注意委托签名必须和目标方法完全一致,包括 ref/out 参数处理。
- 只适用于固定签名的方法,不适合参数数量/类型动态变化的场景
- .NET Framework 下委托绑定比 .NET 5+ 稍慢,但差距已不大
- 缓存生成的委托实例,别每次调用都重新 Create
真正麻烦的从来不是“怎么调”,而是“为什么没调成”——类型加载路径、泛型符号、静态/实例上下文、委托签名对齐,这些细节一旦漏掉一个,调试时间就翻倍。


















