You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在运行时模拟编译器的类型转换行为

运行时模拟编译器类型转换的可行方案

问题背景

需在Unity 6及以上环境下,通过反射在运行时对基础类型、编译时已知的类、结构体、枚举执行隐式/显式类型转换,完全模拟编译器逻辑,且希望框架自动处理转换逻辑,避免手动反射查找。核心诉求是让编译器能识别合法转换对(比如floatint),同时阻止非法转换(比如EnumArray),但泛型方法public TOut Convert<TIn, TOut>(TIn value) => (TOut)value;会因泛型参数无约束直接报错,无法区分合法/非法转换场景。

已尝试方案及痛点

  • Convert.ChangeType:依赖IConvertible接口,很多自定义类型或编译器原生转换不支持,不符合需求。
  • 基于dynamic的转换方法:
    public T Implicit<T>(dynamic value) => value;
    public T Explicit<T>(dynamic value) => (T)value;
    
    这是目前最优方案,但处理null对象时存在缺陷:编译器处理(int)(MyType)(null)会调用对应的op_Explicit重载,但Explicit<int>(default(MyType))会因null丢失类型信息,无法触发正确的转换逻辑。
  • 反射查找op_Implicit/op_Explicit:手动构建了7种基础类型的转换对列表,但Unity版本不支持Numerics接口,无法覆盖所有编译器原生转换逻辑,需大量手动维护。
  • 参考过相关技术讨论,但未找到适配Unity环境的完整方案。

可行解决方案

1. 泛型约束+编译时验证结合运行时反射

针对合法转换对,通过编译时特性+代码生成提前标记支持的类型转换,同时在泛型方法中缩小约束范围,再结合反射兜底处理自定义类型的转换运算符:

// 编译时标记支持转换的类型对
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class SupportedConversionAttribute : Attribute
{
    public Type FromType { get; }
    public Type ToType { get; }
    public bool IsExplicit { get; }

    public SupportedConversionAttribute(Type fromType, Type toType, bool isExplicit = false)
    {
        FromType = fromType;
        ToType = toType;
        IsExplicit = isExplicit;
    }
}

// 转换类
public static class TypeConverter
{
    private static readonly Dictionary<(Type From, Type To), MethodInfo> _conversionMethods = new();

    static TypeConverter()
    {
        // 初始化时反射加载所有标记的转换方法,包括基础类型和自定义类型的op_Implicit/op_Explicit
        var methods = typeof(TypeConverter).GetMethods(BindingFlags.Public | BindingFlags.Static);
        foreach (var method in methods)
        {
            var attrs = method.GetCustomAttributes<SupportedConversionAttribute>();
            foreach (var attr in attrs)
            {
                _conversionMethods.Add((attr.FromType, attr.ToType), method);
            }
        }

        // 加载自定义类型的转换运算符
        var customTypes = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(a => a.GetTypes())
            .Where(t => t.GetMethods(BindingFlags.Public | BindingFlags.Static)
                .Any(m => m.Name == "op_Implicit" || m.Name == "op_Explicit"));

        foreach (var type in customTypes)
        {
            var operators = type.GetMethods(BindingFlags.Public | BindingFlags.Static)
                .Where(m => m.Name is "op_Implicit" or "op_Explicit");

            foreach (var op in operators)
            {
                var fromType = op.GetParameters()[0].ParameterType;
                var toType = op.ReturnType;
                _conversionMethods.Add((fromType, toType), op);
            }
        }
    }

    [SupportedConversion(typeof(float), typeof(int), isExplicit: true)]
    [SupportedConversion(typeof(int), typeof(float), isExplicit: false)]
    // 可添加更多基础类型转换对
    public static TOut Convert<TIn, TOut>(TIn value)
    {
        if (_conversionMethods.TryGetValue((typeof(TIn), typeof(TOut)), out var method))
        {
            return (TOut)method.Invoke(null, new object[] { value });
        }

        // 处理null场景:如果TIn是引用类型且value为null,尝试调用ToType的显式转换
        if (value == null && typeof(TIn).IsClass)
        {
            var explicitOp = typeof(TOut).GetMethod("op_Explicit", 
                BindingFlags.Public | BindingFlags.Static, 
                new[] { typeof(TIn) });
            if (explicitOp != null)
            {
                return (TOut)explicitOp.Invoke(null, new object[] { value });
            }
        }

        throw new InvalidCastException($"不支持从{typeof(TIn)}到{typeof(TOut)}的转换");
    }
}

这种方式既通过编译时标记保证合法转换能被编译器识别,又通过反射兜底处理自定义类型,同时解决了null场景的转换问题。

2. Unity代码生成工具辅助

利用Unity的Roslyn代码生成工具(需确保Unity 6+支持),在编译时自动扫描所有类型的转换运算符,生成对应的转换方法或字典映射,完全模拟编译器的转换逻辑,避免手动维护基础类型转换对。

3. 改进dynamic方案的null处理

针对dynamic方案的null缺陷,调用时显式传递类型信息,确保null能关联到正确的类型:

public static TOut Explicit<TIn, TOut>(TIn value)
{
    if (value == null && typeof(TIn).IsClass)
    {
        // 手动查找TIn到TOut的显式转换运算符
        var op = typeof(TOut).GetMethod("op_Explicit", 
            BindingFlags.Public | BindingFlags.Static, 
            new[] { typeof(TIn) });
        if (op != null)
        {
            return (TOut)op.Invoke(null, new object[] { value });
        }
    }
    dynamic dynValue = value;
    return (TOut)dynValue;
}

这种方式在dynamic的基础上对null场景做了特殊处理,调用时需同时指定TInTOut,让编译器能识别类型约束,同时保证null转换的正确性。

总结

如果要完全模拟编译器逻辑,代码生成+反射兜底是最可靠的方案,既能让编译器识别合法转换,又能自动处理自定义类型的转换运算符;如果追求实现简单,改进后的dynamic方案可以解决null问题,同时保留大部分编译器转换逻辑的兼容性。

内容的提问来源于stack exchange,提问作者E. Zacarias

火山引擎 最新活动