IL Emit 生成委托前必须确认三件事:一要避免 Expression.Compile() 的解释执行低效问题;二需显式校验签名与类型对齐防崩溃;三应选用 DynamicMethod 而非 TypeBuilder 防元数据泄漏。

IL Emit 生成委托前必须确认的三件事
不能直接用 Expression 编译就完事——它在 .NET 6+ 默认走解释执行路径,性能远低于纯 IL;也不能盲目调用 DynamicMethod.CreateDelegate 就以为安全,没做类型校验或签名对齐会 runtime 崩溃。真正高性能且可控的路径,是手写 IL + 显式验证签名 + 使用 DynamicMethod 绑定到闭包或实例。
关键判断:你是否需要「绕过 JIT 预编译开销」+「复用同一段 IL 多次调用」+「避免 Expression.Compile() 的缓存/锁竞争」?如果是,才值得上 IL Emit。
用 DynamicMethod 写最简 IL 委托(无参数、无返回)
这是最容易上手且不会因签名错导致 VerificationException 的起点。注意:.NET 5+ 要求 DynamicMethod 必须指定 owner 类型(哪怕为 null),否则抛 ArgumentException。
-
DynamicMethod构造时传typeof(void)作返回类型,Type.EmptyTypes作参数数组 - 用
ILGenerator的Emit(OpCodes.Ret)结束方法体,不能漏 - 调用
CreateDelegate时必须传具体委托类型,如Action,不能只传typeof(Delegate)
var dm = new DynamicMethod("FastNoOp", typeof(void), Type.EmptyTypes, typeof(Program));
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ret);
var action = (Action)dm.CreateDelegate(typeof(Action));
action(); // 安全执行
带参数和返回值的 IL 委托:签名对齐是崩溃主因
常见错误是参数顺序/类型不匹配:比如 C# 委托声明为 Func<int, string, bool>,但 IL 中用 ldarg.0 取了 string 当 int 用,JIT 会直接拒绝验证,抛 InvalidProgramException。
正确做法:
- 用
ldarg.0~ldarg.3仅限前 4 个参数;超过用ldarg N(N 从 0 开始) - 返回值类型必须与
DynamicMethod构造时第 1 个参数严格一致(含可空性,int?≠int) - 若需访问实例字段,
DynamicMethod的owner参数必须设为该实例类型,且首参数隐含this(即签名中要多写一个参数)
例如生成 Func<double, double, double> 求和委托:
var dm = new DynamicMethod("_Add", typeof(double), new[] { typeof(double), typeof(double) }, typeof(Math));
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
var add = (Func<double, double, double>)dm.CreateDelegate(typeof(Func<double, double, double>));
为什么不用 AssemblyBuilder 或 TypeBuilder?
因为它们生成的是「永久性」类型,即使只用一次也会常驻内存,GC 不回收,容易造成元数据泄漏(尤其在高频动态生成场景下)。而 DynamicMethod 是轻量级、无类型绑定、生命周期由 delegate 引用控制的方案。
但要注意一个隐藏约束:DynamicMethod 无法访问 private 成员(除非 owner 是当前类且运行在 TrustLevel 允许环境),也无法生成泛型方法主体。如果遇到需要闭包捕获或泛型推导的场景,得退回到 Expression + Compile(),或者改用 Reflection.Emit + TypeBuilder 并自行管理类型卸载(.NET 5+ 支持 AssemblyLoadContext.Unload(),但代价高)。
真正难的不是写 IL,而是让 IL 在所有目标运行时(特别是 AOT 编译的 .NET Native 或 iOS)都能被接受——此时应优先考虑 Source Generators 替代运行时 emit。



















