Reflection.Emit实现值转换委托时的InvalidProgramException问题排查
开发面向嵌入式Linux平台的Unity工具,该环境对运行时分配极度敏感(GC会导致卡顿),要求完全避免运行时分配/释放操作。工具核心功能是类型转换,以下是迭代过程中遇到的问题:
原有实现
最初的POC采用显式转换接口:
public interface IValue { bool AsInt(out int result); bool AsFloat(out float result); bool AsDouble(out double result); //... 为所有支持的值类型添加显式转换方法 } public abstract class Value<T> : IValue { [SerializeField] private T m_Value; public T TypedValue { get => m_Value; set => m_Value = value; } private bool TryCast<TTarget>(out TTarget result) { if (m_Value is TTarget target) { result = target; return true; } result = default; return false; } public virtual bool AsInt(out int result) { return TryCast(out result); } public virtual bool AsFloat(out float result) { return TryCast(out result); } public virtual bool AsDouble(out double result) { return TryCast(out result); } //... }
实现示例:
[Serializable] public class BoolOriginal : Value<bool> { public override bool AsInt(out int result) { result = TypedValue ? 1 : 0; return true; } public override bool AsFloat(out float result) { result = TypedValue ? 1.0f : 0.0f; return true; } public override bool AsDouble(out double result) { result = TypedValue ? 1.0 : 0.0; return true; } }
问题:新增类型需硬编码修改接口和基类,但工具作为只读包无法后续调整。
初次改进尝试
改用泛型接口解决扩展性,但引入装箱分配:
public interface IValue { bool As<TTarget>(out TTarget result); } public abstract class Value<T> : IValue { [SerializeField] private T m_Value; public T TypedValue { get => m_Value; set => m_Value = value; } public bool As<TTarget>(out TTarget result) { if (TypedValue is TTarget target) { result = target; return true; } if (Conversions.TryGetValue(typeof(TTarget), out var conversion)) { result = (TTarget)conversion(TypedValue); return true; } if (k_DefaultConversions.TryGetValue(typeof(TTarget), out var defaultConversion)) { result = (TTarget)defaultConversion(TypedValue); return true; } result = default; return false; } private static readonly IReadOnlyDictionary<Type, Func<T, object>> k_DefaultConversions = new Dictionary<Type, Func<T, object>> { { typeof(string), value => value.ToString() }, }; protected abstract IReadOnlyDictionary<Type, Func<T, object>> Conversions { get; } }
实现示例:
public class BoolPlain : Value<bool> { private static readonly IReadOnlyDictionary<Type, Func<bool, object>> k_Conversions = new Dictionary<Type, Func<bool, object>> { { typeof(int), value => value ? 1 : 0 }, { typeof(float), value => value ? 1.0f : 0.0f }, { typeof(double), value => value ? 1.0 : 0.0 } }; protected override IReadOnlyDictionary<Type, Func<bool, object>> Conversions => k_Conversions; }
问题:Func<T, object>会导致值类型装箱,不符合无分配要求。
Linq Expression优化
用Linq Expression减少装箱,将分配减半:
public abstract class Value<T> : IValue { private T m_Value; public T TypedValue { get => m_Value; set => m_Value = value; } public bool As<TTarget>(out TTarget result) { if (TypedValue is TTarget target) { result = target; return true; } if (Conversions.TryGetValue(typeof(TTarget), out var conversion) && conversion is Func<T, TTarget> converter) { result = converter(TypedValue); return true; } if (k_DefaultConversions.TryGetValue(typeof(TTarget), out var defaultConversion) && defaultConversion is Func<T, TTarget> defaultConverter) { result = defaultConverter(TypedValue); return true; } result = default; return false; } protected static Delegate CreateConverter<TOutput>(Func<T, TOutput> converter) { var input = Expression.Parameter(typeof(T), "input"); var body = Expression.Invoke(Expression.Constant(converter), input); var lambda = Expression.Lambda(body, input); return lambda.Compile(); } private static readonly IReadOnlyDictionary<Type, Delegate> k_DefaultConversions = new Dictionary<Type, Delegate> { { typeof(string), CreateConverter<string>(v => v.ToString()) }, }; protected abstract IReadOnlyDictionary<Type, Delegate> Conversions { get; } }
实现示例:
public class BoolValue : Value<bool> { private static readonly IReadOnlyDictionary<Type, Delegate> k_Conversions = new Dictionary<Type, Delegate> { { typeof(int), CreateConverter<int>(v => v ? 1 : 0) }, { typeof(float), CreateConverter<float>(v => v ? 1.0f : 0.0f) }, { typeof(double), CreateConverter<double>(v => v ? 1.0 : 0.0) } }; protected override IReadOnlyDictionary<Type, Delegate> Conversions => k_Conversions; }
当前问题:Reflection.Emit报错
尝试用Reflection.Emit实现无分配转换委托,代码如下:
private static readonly Type valueType = typeof(T); protected static Delegate CreateConverter<TOutput>(Func<T, TOutput> converter) { var outType = typeof(TOutput); var method = new DynamicMethod( "Convert_" + valueType.Name + "_To_" + outType.Name, outType, new[] { valueType }, typeof(Value<>).Module, true); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.EmitCall(OpCodes.Call, converter.Method, null); il.Emit(OpCodes.Ret); return method.CreateDelegate(typeof(Func<T, TOutput>)); }
运行时抛出InvalidProgramException: Invalid IL code in (wrapper dynamic-method) object:Convert_Boolean_To_String (bool): IL_0001: call 0x00000001,尝试替换OpCodes.Call为OpCodes.Callvirt仍无效。
问题提问
- 上述Reflection.Emit代码的错误原因是什么?是否与使用开放泛型模块
typeof(Value<>).Module有关? - 有没有其他兼具灵活性且完全无运行时分配的替代方案?
回答
一、Reflection.Emit错误原因与修正
核心错误点:
- 开放泛型模块引用错误:
typeof(Value<>).Module引用的是开放泛型类型的模块,而实际运行的是闭合泛型Value<T>,两者模块可能不匹配,导致动态方法无法正确访问目标方法。应替换为typeof(Value<T>).Module(闭合泛型类型)。 - 调用指令选择错误:
- 如果
converter.Method是实例方法(例如值类型的ToString()),必须使用OpCodes.Callvirt而非OpCodes.Call,因为实例方法需要虚方法调度(即使是值类型的方法,CLR也要求用Callvirt调用继承自Object的虚方法)。 - 如果是静态方法,则使用
OpCodes.Call。
- 如果
修正后的CreateConverter方法:
private static readonly Type valueType = typeof(T); protected static Delegate CreateConverter<TOutput>(Func<T, TOutput> converter) { var outType = typeof(TOutput); var targetMethod = converter.Method; // 使用闭合泛型类型的模块 var method = new DynamicMethod( $"Convert_{valueType.Name}_To_{outType.Name}", outType, new[] { valueType }, typeof(Value<T>).Module, true); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // 加载输入参数 // 根据方法是否静态选择调用指令 if (targetMethod.IsStatic) { il.Emit(OpCodes.Call, targetMethod); } else { il.Emit(OpCodes.Callvirt, targetMethod); } il.Emit(OpCodes.Ret); return method.CreateDelegate(typeof(Func<T, TOutput>)); }
额外注意:如果传入的converter是捕获了变量的Lambda表达式(闭包),converter.Method会是编译器生成的实例方法,此时需要加载闭包实例才能调用,但这种情况会引入分配,因此建议仅使用无捕获的静态Lambda表达式。
二、无运行时分配的替代方案
1. 编译时代码生成(Source Generator)
方案:使用Unity支持的C# Source Generator,在编译时为每个Value<T>自动生成所有转换方法的实现。
优势:
- 完全无运行时分配,性能最优。
- 无需反射或动态方法,符合嵌入式环境要求。
- 扩展性强:新增转换类型时,只需添加Generator规则,无需修改原有代码。
示例思路: - 定义属性标记需要支持的转换类型。
- Generator扫描所有
Value<T>子类,自动生成As<TTarget>方法的强类型实现。
2. 静态泛型缓存字典
方案:使用静态泛型类缓存强类型转换委托,避免装箱和动态方法开销。
public abstract class Value<T> : IValue { private T m_Value; public T TypedValue { get => m_Value; set => m_Value = value; } // 泛型缓存类,每个<T, TTarget>组合对应唯一实例 private static class ConverterCache<TTarget> { public static readonly Func<T, TTarget> Converter; static ConverterCache() { // 初始化时查找对应的转换委托 if (typeof(T) == typeof(TTarget)) { Converter = v => (TTarget)(object)v; // 无装箱强制转换(仅当T和TTarget类型相同时) } else if (Conversions.TryGetValue(typeof(TTarget), out var del) && del is Func<T, TTarget> func) { Converter = func; } else if (DefaultConversions.TryGetValue(typeof(TTarget), out var defaultDel) && defaultDel is Func<T, TTarget> defaultFunc) { Converter = defaultFunc; } else { Converter = null; } } } public bool As<TTarget>(out TTarget result) { if (typeof(T) == typeof(TTarget)) { result = (TTarget)(object)m_Value; return true; } var converter = ConverterCache<TTarget>.Converter; if (converter != null) { result = converter(m_Value); return true; } result = default; return false; } protected abstract IReadOnlyDictionary<Type, Delegate> Conversions { get; } private static readonly IReadOnlyDictionary<Type, Delegate> DefaultConversions = new Dictionary<Type, Delegate> { { typeof(string), (Func<T, string>)(v => v.ToString()) } }; }
优势:
- 无运行时分配,所有缓存都在静态构造时完成。
- 强类型委托调用,无装箱开销。
- 实现简单,无需依赖动态方法或反射。
3. 显式转换接口注册
方案:定义IConverter<TSource, TTarget>接口,实现所有转换逻辑,通过静态字典注册,调用时直接强类型转换。
public interface IConverter<TSource, TTarget> { bool Convert(TSource source, out TTarget result); } // 实现示例 public class BoolToIntConverter : IConverter<bool, int> { public bool Convert(bool source, out int result) { result = source ? 1 : 0; return true; } } public abstract class Value<T> : IValue { private T m_Value; public T TypedValue { get => m_Value; set => m_Value = value; } private static readonly Dictionary<Type, object> s_converters = new Dictionary<Type, object>(); private static readonly Dictionary<Type, object> s_defaultConverters = new Dictionary<Type, object> { { typeof(string), new ToStringConverter<T>() } }; static Value() { // 注册自定义转换器(可通过配置或属性扫描) s_converters.Add(typeof(int), new BoolToIntConverter()); } public bool As<TTarget>(out TTarget result) { if (typeof(T) == typeof(TTarget)) { result = (TTarget)(object)m_Value; return true; } if (s_converters.TryGetValue(typeof(TTarget), out var obj) && obj is IConverter<T, TTarget> converter) { return converter.Convert(m_Value, out result); } if (s_defaultConverters.TryGetValue(typeof(TTarget), out var defaultObj) && defaultObj is IConverter<T, TTarget> defaultConverter) { return defaultConverter.Convert(m_Value, out result); } result = default; return false; } } // 默认ToString转换器 public class ToStringConverter<T> : IConverter<T, string> { public bool Convert(T source, out string result) { result = source.ToString(); return true; } }
优势:
- 完全无运行时分配,所有转换器都是静态实例。
- 扩展性强,新增转换器只需实现接口并注册。
- 逻辑清晰,易于调试和维护。
内容的提问来源于stack exchange,提问作者derHugo

