严重的错误与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); }
我很惊讶这个编译,当运行, 写出2
。 int
(值为2
)到NumberFixedPoint2
的转换在这里很重要。 (接受System.Decimal
的WriteLine
的重载是首选,以防有人奇怪。)
为什么在地球上是从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#编译器中解除转换的人)知道这些错误的原因吗? 就像,我们的代码中没有遇到什么足够的条件? 因为NumberFixedPoint2
types实际上是我们用真实代码(pipe理其他人的钱和东西)的东西。
你的第二部分(使用可空types)看起来与当前编译器中的这个已知错误非常相似。 来自Connect问题的回应:
尽pipe目前我们还没有计划在Visual Studio的下一个版本中解决这个问题,但是我们计划在Roslyn中调查一个修补程序
因此,这个错误将有望在未来的Visual Studio版本和编译器中得到纠正。
我只是回答问题的第一部分。 (我build议第二部分应该是一个单独的问题,这更可能是一个错误。)
只有从decimal
到int
的显式转换,但是这个转换正在你的代码中隐式调用。 转换发生在这个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节的方式工作。 我不打算把所有的文字都抄下来,因为这会很枯燥 – 我们的情况是什么样的结果。 同样,我不会使用下标,因为它们不适合代码字体:)
- 确定types
S0
和T0
:S0
是decimal
,T0
是NumberFixedPoint2
。 - find一组types
D
,从中可以使用已定义的转换运算符:只要{ decimal, NumberFixedPoint2 }
- find一组适用的用户定义和提升的转换运算符
U
decimal
包含int
(第6.4.3节),因为存在从int
到decimal
的标准隐式转换。 所以显式转换运算符在U
,并且确实是U
的唯一成员 - find
U
运营商的最具体的源typesSx
- 运算符不会从
S
(decimal
)转换,所以第一个项目符号不存在 - 运算符不会从包含
S
的types转换(decimal
包含int
,而不是相反),所以第二个子项不在 - 这只是第三个子弹,其中谈到“最包容的types” – 好吧,我们只有一个types,所以没关系:
Sx
是int
。
- 运算符不会从
- find
U
运营商的最具体的目标typesTx
- 运算符直接转换为
NumberFixedPoint2
因此Tx
是NumberFixedPoint2
。
- 运算符直接转换为
- find最具体的转换运算符:
-
U
包含一个运算符,它确实从Sx
转换为Tx
,所以这是最具体的运算符
-
- 最后,应用转换:
- 如果
S
不是Sx
,则执行从S
到Sx
的标准显式转换。 (所以这是decimal
的int
。) - 调用最具体的用户定义的转换运算符(您的运算符)
-
T
是Tx
所以不需要第三个项目符号的转换
- 如果
粗体的这一行是确认一个标准的显式转换确实是可行的,而实际上只有一个不同types的显式转换是可行的。
1我发现至less令人惊讶。 我不知道以前看到这个。