严重的错误与int提升/可空转换,允许从十进制转换

我认为这个问题会使我立即在Stack Overflow上成名。

假设你有以下types:

// represents a decimal number with at most two decimal places after the period struct NumberFixedPoint2 { decimal number; // an integer has no fractional part; can convert to this type public static implicit operator NumberFixedPoint2(int integer) { return new NumberFixedPoint2 { number = integer }; } // this type is a decimal number; can convert to System.Decimal public static implicit operator decimal(NumberFixedPoint2 nfp2) { return nfp2.number; } /* will add more nice members later */ } 

这样写就是只允许安全的转换不会失去精度。 但是,当我尝试这个代码:

  static void Main() { decimal bad = 2.718281828m; NumberFixedPoint2 badNfp2 = (NumberFixedPoint2)bad; Console.WriteLine(badNfp2); } 

我很惊讶这个编译,当运行, 写出2int (值为2 )到NumberFixedPoint2的转换在这里很重要。 (接受System.DecimalWriteLine的重载是首选,以防有人奇怪。)

为什么在地球上是从decimal转换为NumberFixedPoint2允许? (顺便说一下,在上面的代码中,如果NumberFixedPoint2从一个结构体更改为一个类,则没有任何变化。)

你知道,如果C#语言规范说,从int到自定义types的隐式转换“隐含”是否存在从小decimal到该自定义types的“直接”显式转换?

它变得更糟糕。 试试这个代码:

  static void Main() { decimal? moreBad = 7.3890560989m; NumberFixedPoint2? moreBadNfp2 = (NumberFixedPoint2?)moreBad; Console.WriteLine(moreBadNfp2.Value); } 

如您所见,我们已经(取消) Nullable<>转换。 但是,是的,这是编译。

x86 “平台”中编译时,此代码写出一个不可预知的数值。 哪一个不时变化。 举个例子,有一次我得到了2289956 。 现在,这是一个严重的错误!

当为x64平台编译时,上面的代码使用一个System.InvalidProgramException使应用程序崩溃,并且消息Common Language Runtime检测到一个无效的程序。 根据InvalidProgramException类的文档:

通常这表示在生成程序的编译器中的错误。

有人(比如Eric Lippert,或者曾经在C#编译器中解除转换的人)知道这些错误的原因吗? 就像,我们的代码中没有遇到什么足够的条件? 因为NumberFixedPoint2types实际上是我们用真实代码(pipe理其他人的钱和东西)的东西。

你的第二部分(使用可空types)看起来与当前编译器中的这个已知错误非常相似。 来自Connect问题的回应:

尽pipe目前我们还没有计划在Visual Studio的下一个版本中解决这个问题,但是我们计划在Roslyn中调查一个修补程序

因此,这个错误将有望在未来的Visual Studio版本和编译器中得到纠正。

我只是回答问题的第一部分。 (我build议第二部分应该是一个单独的问题,这更可能是一个错误。)

只有从decimalint显式转换,但是这个转换正在你的代码中隐式调用。 转换发生在这个IL:

 IL_0010: stloc.0 IL_0011: ldloc.0 IL_0012: call int32 [mscorlib]System.Decimal::op_Explicit(valuetype [mscorlib]System.Decimal) IL_0017: call valuetype NumberFixedPoint2 NumberFixedPoint2::op_Implicit(int32) 

我相信这是根据规范的正确行为,即使这是令人惊讶的1 。 让我们按照C#4规范(用户定义的显式转换)第6.4.5节的方式工作。 我不打算把所有的文字都抄下来,因为这会很枯燥 – 我们的情况是什么样的结果。 同样,我不会使用下标,因为它们不适合代码字体:)

  • 确定typesS0T0S0decimalT0NumberFixedPoint2
  • find一组typesD ,从中可以使用已定义的转换运算符:只要{ decimal, NumberFixedPoint2 }
  • find一组适用的用户定义和提升的转换运算符U decimal 包含 int (第6.4.3节),因为存在从intdecimal的标准隐式转换。 所以显式转换运算符在U ,并且确实 U的唯一成员
  • findU运营商的最具体的源typesSx
    • 运算符不会从Sdecimal )转换,所以第一个项目符号不存在
    • 运算符不会从包含S的types转换( decimal包含int ,而不是相反),所以第二个子项不在
    • 这只是第三个子弹,其中谈到“最包容的types” – 好吧,我们只有一个types,所以没关系: Sxint
  • findU运营商的最具体的目标typesTx
    • 运算符直接转换为NumberFixedPoint2因此TxNumberFixedPoint2
  • find最具体的转换运算符:
    • U包含一个运算符,它确实从Sx转换为Tx ,所以这是最具体的运算符
  • 最后,应用转换:
    • 如果S不是Sx ,则执行从SSx的标准显式转换。 (所以这是decimalint 。)
    • 调用最具体的用户定义的转换运算符(您的运算符)
    • TTx所以不需要第三个项目符号的转换

粗体的这一行是确认一个标准的显式转换确实是可行的,而实际上只有一个不同types的显式转换是可行的。


1我发现至less令人惊讶。 我不知道以前看到这个。