.NET Reflection Emit编写MSIL方法报错,求排查及性能咨询
Reflection Emit 动态委托错误排查及性能分析
一、InvalidProgramException 错误排查
这类异常本质是生成的IL指令不符合CLR执行规范,常见问题及修复方案如下:
常见错误点及修复
栈操作不平衡
IL基于栈执行,每一步操作必须保证栈的压入/弹出数量匹配。比如调用List.Add后栈需清空,否则后续指令会触发异常。正确流程需严格遵循「加载对象→获取属性值→装箱(值类型)→加载List→调用Add」,确保栈状态始终合法。值类型未装箱
值类型(如int、DateTime)直接转换为object时必须使用Box指令,否则CLR无法识别类型。遍历属性时需判断PropertyType.IsValueType,若是则插入OpCodes.Box指令完成装箱。委托签名不匹配
动态方法的参数、返回值类型必须与目标委托完全一致。比如要生成Func<T, List<object>>,动态方法的参数必须为T类型,返回值必须是List<object>。方法调用指令错误
属性get方法为虚方法时用Callvirt,非虚方法用Call;Callvirt对非虚方法也能正常执行,但调用静态方法必须用Call。
正确实现示例
using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; public static class PropertyExtractorGenerator { public static Func<T, List<object>> CreatePropertyExtractor<T>() { var targetType = typeof(T); var properties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance); var dynamicMethod = new DynamicMethod( $"Extract_{targetType.Name}_Props", typeof(List<object>), new[] { targetType }, typeof(PropertyExtractorGenerator).Module, skipVisibility: true); var il = dynamicMethod.GetILGenerator(); // 初始化List<object>并存入局部变量 il.Emit(OpCodes.Newobj, typeof(List<object>).GetConstructor(Type.EmptyTypes)); var listLocal = il.DeclareLocal(typeof(List<object>)); il.Emit(OpCodes.Stloc, listLocal); foreach (var prop in properties) { // 加载输入对象(参数0) il.Emit(OpCodes.Ldarg_0); // 调用属性get方法 il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // 值类型装箱 if (prop.PropertyType.IsValueType) { il.Emit(OpCodes.Box, prop.PropertyType); } // 加载List实例并调用Add方法 il.Emit(OpCodes.Ldloc, listLocal); il.Emit(OpCodes.Callvirt, typeof(List<object>).GetMethod("Add", new[] { typeof(object) })); } // 返回List实例 il.Emit(OpCodes.Ldloc, listLocal); il.Emit(OpCodes.Ret); return (Func<T, List<object>>)dynamicMethod.CreateDelegate(typeof(Func<T, List<object>>)); } } // 测试用类 public class DemoClass { public int Id { get; set; } public string Name { get; set; } public DateTime BirthDate { get; set; } } // 使用方式 var extractor = PropertyExtractorGenerator.CreatePropertyExtractor<DemoClass>(); var demo = new DemoClass { Id = 101, Name = "Alice", BirthDate = new DateTime(1990, 1, 1) }; var propValues = extractor(demo);
二、性能问题分析
1. 生成委托的调用性能
与编译后的静态代码几乎完全一致。动态生成的IL会被CLR的JIT编译器编译为原生机器码,后续调用委托时直接执行原生代码,无反射调用的额外开销。
2. 反射获取成员信息的性能
仅在委托生成阶段有一次开销(调用GetProperties获取属性信息),后续调用委托时不会再触发反射操作。若对同一类型多次调用提取器,初始化阶段的开销可忽略;追求极致性能可缓存生成的委托(比如用静态字典存储类型与对应委托的映射)。
内容的提问来源于stack exchange,提问作者user2173353
相关产品推荐
相关产品推荐

