You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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仍无效。

问题提问

  1. 上述Reflection.Emit代码的错误原因是什么?是否与使用开放泛型模块typeof(Value<>).Module有关?
  2. 有没有其他兼具灵活性且完全无运行时分配的替代方案?

回答

一、Reflection.Emit错误原因与修正

核心错误点:
  1. 开放泛型模块引用错误:typeof(Value<>).Module引用的是开放泛型类型的模块,而实际运行的是闭合泛型Value<T>,两者模块可能不匹配,导致动态方法无法正确访问目标方法。应替换为typeof(Value<T>).Module(闭合泛型类型)。
  2. 调用指令选择错误:
    • 如果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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 23:55:56