MethodInfo.Invoke 的正确姿势是优先用 CreateDelegate,否则需严格匹配参数类型与数量,ref/out 参数须用变量封装并手动处理返回值,避免裸 null 和数组误传。

反射调用带参数的方法:MethodInfo.Invoke 的正确姿势
直接说结论:别用 MethodInfo.Invoke(null, args) 硬传数组,尤其当方法有 ref、out 或可空值类型参数时,大概率抛 TargetParameterCountException 或 ArgumentException。
根本原因是 Invoke 对参数的绑定是严格按签名来的,不是“把数组元素挨个塞进去”那么简单。比如目标方法是 void M(int a, string b),你传 new object[] { 42, null } 没问题;但如果是 void M(ref int x),你就得传一个 object[] 包着一个 int 变量的引用——而 C# 的 ref 不能直接装箱。
- 优先用
MethodInfo.CreateDelegate(.NET Core 2.1+ / .NET 5+),类型安全、快、支持ref参数(需匹配委托签名) - 如果必须用
Invoke,确保args数组长度和类型完全匹配方法签名,null值要显式转成typeof(string)这类可空引用类型,而不是裸null - 调用实例方法时,第一个参数必须是目标实例(不能为
null),静态方法才传null作调用者
处理 ref 和 out 参数:别指望自动解包
反射不会帮你拆箱 ref 或捕获 out 结果——它只认你传进去的那个 object 容器。常见错误是传 new object[] { 123 } 给 void F(ref int x),结果运行时报错说“无法将 Int32 转换为 Int32&”。
-
ref参数必须用对应类型的变量地址封装:先声明int value = 42;,再传new object[] { value },调用完再从返回的object[]里取回新值(value本身不会变,但数组首项会更新) -
out参数同理,但初始值可以是任意值(甚至null),只要数组位置对、类型对 - 更稳妥的做法是写个辅助方法,用表达式树或
DynamicMethod生成专用委托,绕过反射参数绑定的坑
动态执行代码:比反射更轻量的替代方案
如果目标只是“根据字符串跑一段逻辑”,Assembly.Load + Type.GetMethod + Invoke 是重武器,也容易因版本、加载上下文、权限出问题。很多场景其实不需要完整反射。
- 简单计算或条件判断,用
System.Linq.Expressions构建表达式树(如Expression.Lambda编译成Func<T>),安全且快 - 配置驱动的策略调用,提前把合法方法名映射到委托字典里:
var handlers = new Dictionary<string, Action> { ["Save"] = () => _svc.Save() };,查表执行,零反射开销 - 真要执行任意 C# 代码片段(比如用户输入的表达式),用
Microsoft.CodeAnalysis.CSharp.Scripting(Roslyn Scripting),但注意沙箱隔离和超时控制
性能与兼容性:别在热路径上反复反射
每次 GetMethod + Invoke 都有显著开销:元数据查找、参数验证、堆栈检查。.NET 6+ 虽有缓存优化,但没到能忽略的程度。
- 高频调用的方法,务必缓存
MethodInfo或(更推荐)缓存Delegate实例,避免重复查找 - .NET Framework 下
Delegate.CreateDelegate不支持泛型方法,得用DynamicMethod或表达式树补位 - Unity IL2CPP 构建时默认剥离反射元数据,
GetMethod可能返回null,需在link.xml里保留类型和方法
反射不是黑魔法,它暴露的是编译期已知的契约。参数类型、调用约定、实例状态,每一条都得对得上,少一个就卡住。最常被忽略的,是把“能调通”当成“调得稳”——而线上服务崩一次,往往就因为某个 Nullable<int> 参数传了 null 却没做 typeof(int?) 显式转换。










