这是一个ExpressionTrees的bug吗?#2

10

看起来 ExpressionTrees 编译器在许多方面应该与 C# 规范相近,但与 C# 不同的是,它不支持从 decimal 到任何 enum-type 的转换:

using System;
using System.Linq.Expressions;

class Program
{
  static void Main()
  {
    Func<decimal, ConsoleColor> converter1 = x => (ConsoleColor) x;
    ConsoleColor c1 = converter1(7m); // fine

    Expression<Func<decimal, ConsoleColor>> expr = x => (ConsoleColor) x;

    // System.InvalidOperationException was unhandled
    // No coercion operator is defined between types
    // 'System.Decimal' and 'System.ConsoleColor'.

    Func<decimal, ConsoleColor> converter2 = expr.Compile();

    ConsoleColor c2 = converter2(7m);
  }
}

除了C#规范中解释的那些显式转换之外,还存在其他很少使用的C#显式转换,比如double -> 枚举类型,但不包括decimal -> 枚举类型。这是一个错误吗?

2个回答

16

这可能是一个错误,而且很可能是我的错。对此感到抱歉。

在编译器和运行时中,正确构建表达式树代码的最难部分之一是准确进行十进制转换,因为十进制转换实际上是在运行时作为用户定义的转换实现的,但由编译器视为内置转换。十进制是唯一具有这种属性的类型,因此分析器中有各种特殊用途的工具来处理这些情况。实际上,分析器中还有一个名为IsEnumToDecimalConversion的方法来处理可空枚举值到可空十进制值的特殊情况,这是一个相当复杂的特殊情况。

很有可能我没有考虑到某些反向情况,从而生成了错误的代码。谢谢你的留言; 我会将其发送给测试团队,看是否可以重现。很有可能,如果这真的是一个真正的Bug,那么它不会在C#4的初始版本中修复; 目前我们只修复“用户被编译器电击”的漏洞,以使版本稳定。


我不知道在开发C#语言的过程中会伤害到人类 :) - Joan Venge
十进制转换实际上是在运行时作为用户定义的转换来实现的,但由编译器视为内置转换。这意味着什么,为什么要这样做? - Brian
2
@Brian:当你进行表示更改的转换,比如从int到double时,有一个IL指令可以完成这个转换。当你从decimal到double时,我们实际上会生成调用方法来完成转换的代码;CLR中没有内置的decimal转换指令。但是从语言的角度来看,我们希望decimal转换看起来像是内置于语言的转换;我们对内置和用户提供的转换有不同的规则。因此,我们必须构建一些特殊的场景来隐藏在decimal背后发生的事情。 - Eric Lippert

3

目前还没有真正的答案,我正在调查,但第一行被编译为:

Func<decimal, ConsoleColor> converter1 = x => (ConsoleColor)(int)x;

如果您尝试从先前的 lambda 创建表达式,它将起作用。
编辑:在 C# 规范的 §6.2.2 中,您可以阅读到:
显式枚举转换是通过将任何参与的枚举类型视为该枚举类型的基础类型来处理的,然后在生成的类型之间执行隐式或显式数字转换。例如,给定一个具有 int 作为底层类型的 E 枚举类型,从 E 到 byte 的转换被处理为从 int 到 byte 的显式数字转换(§6.2.1),从 byte 到 E 的转换被处理为从 byte 到 int 的隐式数字转换(§6.1.2)。
因此,特定处理枚举到十进制的显式转换,这就是为什么会得到嵌套转换(int 然后是 decimal)。但我不明白为什么编译器在两种情况下没有以相同的方式解析 lambda 主体。

2
编译器可能会在另一个阶段发出嵌套转换。在这种情况下,它只创建一个转换节点,在运行时失败。是编译器应该发出嵌套转换的错误,还是表达式API应该理解十进制到枚举的转换的错误,留给读者自己判断。我个人认为,csc有责任发出正确的转换节点。 - Jb Evain
我同意。实际上,在“expr = lambda”行上会出现编译错误。因此,编译器甚至不尝试发出额外的Convert节点或任何其他内容;它认为lambda主体无效,但根据C#规范并非如此。 - Romain Verdier
对于 double -> enum-type 的转换,csc 不会发出 Convert double -> int,而是直接发出 double -> enum-type,ExpressionTrees 编译器可以很好地理解这一点... - controlflow
1
控制流:这是因为十进制不是传统的值类型。它转换为 int 不是通过标准转换,而是通过编译器发出的运算符进行转换。 Romain:实际上,我认为编译器会发出节点“decimal -> enum”并认为它是有效的。然后表达式树工厂方法会退出。 - Jb Evain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接