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

编程语言 2026-07-10

我正在尝试做的事情

我需要在运行时对基本类型以及代码中可能存在的其他类型(在编译时已知),包括类、结构体和枚举,执行隐式和显式类型转换(通过反射)。我想要模拟编译器的行为。它需要与Unity兼容,至少到版本6,这意味着某些新特性不可用。

我在做一些变通处理,这些并不是编译器会真正做的。我希望框架能够自动解析这些类型转换,而不是让我逐个通过反射手动查找。

我的问题

我正在寻找强制编译器在运行时“发现”类型并执行通常在编译时就会做的事的方法。例如:

public TOut Convert<TIn, TOut>(TIn value) => (TOut)value;

显然这会导致编译错误,因为 TInTOut 不兼容。

然而,Convert<float, int>(10f) 可以在编译时通过 MakeGeneric 知道,或在一定程度上是可行的。虽然也存在某些情形无法做到的情况(例如 Convert<Enum, Array>)。此时编译器不会允许。

有没有办法让它起作用?也许有一条规则?一个接口?其他方法?

到目前为止我已经做了什么

  • Convert.ChangeType 需要 IConvertible。这不是我想要的。

  • public T Implicit<T>(dynamic value) => value; public T Explicit<T>(dynamic value) => (T)value; 目前是最佳选项。
    它能工作,除了空引用的情况:当编译器对 (int)(MyType)(null) 调用 op_Explicit 的重载时,Explicit<int>(default(MyType)) 不起作用,因为 null 没有类型。我没有发现其他问题。

  • 目前,我使用 GetMethodsGetMember 在类型中查找 op_Implicitop_Explicit 方法,然后再按参数筛选列表。遗憾的是,在我当前版本的框架中,原始数值的Numerics接口不可用,因此在原始类型上无法依赖这种方法。我已经用lambda构建了一个可能的类型转换清单,用于在编译时解析类型。再次,这样做是可行的,但我只选择了7 个原始类型,因为类的大小限制,因此它并不完全等同于编译器应有的做法。还有一个类,包含所有的配对(int→long、int→float、int→double、long→float、long→double、float→double等)。

  • 我查阅了几篇帖子(例如Jon Skeet的这个回答),它对动态方面有所帮助。

解决方案

问题的根源在于转换运算符是静态方法,这意味着它们在编译时就被解析。但你在编译时并不知道类型。

如果把C#泛型与C++模板相比(你的 Convert 方法在前者中会按预期工作),请记住,C++编译器会确定你使用的每一个模板组合,并将指令写入二进制中。相比之下,C#的泛型是在即时编译(JIT)阶段解析的,这一阶段通常发生在最终用户的设备上,距离编译已经很久。

C#的作者选择不在JIT时解析静态调用,我猜想这是为了不降低泛型的可靠性。无法保证任意两种类型都具备转换运算符,这意味着如果允许在泛型类型之间进行静态转换,.NET也不一定能为泛型代码生成指令。这对开发者来说既棘手又难以排除。

解决方案是在运行时检查类型是否可转换。基于此,你已经尝试的三种方案都是有效的,但在不同情况下会各自失败。

  • dynamic 充当一个运行时编译器,帮助你解析那些静态方法。我相信你可以解决 null 的问题。听起来很棒,但请注意,运行时编译被视为不安全,在一些平台上(如iOS)甚至被禁止。

  • 对于没有编译时调用的方法,可能会从打包的程序集被“裁剪”掉,因此在反射(或 dynamic)中找不到。

  • IConvertible 现在成为一个不错的选择:它不涉及运行时代码生成,编译器可以看到你正在使用该接口,因此不会裁剪掉实现它的任何方法。不过你可能有拒绝它的理由。

也许你愿意让代码的使用者在这些限制内工作。如果不愿意,还有一个你没提及、肯定能始终奏效的选项:让调用方自己来完成转换。你可以通过要求一个与 Convert.ChangeType 签名相同的委托来实现。这样调用方就会用普通的、非泛型的C#代码来完成所需的工作。大概你和调用方协作起来,应该能够覆盖所有需要转换的类型?

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章