Jep*_*sen 59 .net c# nullable compiler-bug implicit-conversion
我认为这个问题会让我在Stack Overflow上立刻成名.
假设您有以下类型:
// 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 */
}
Run Code Online (Sandbox Code Playgroud)
它的编写使得只允许安全转换不会丢失精度.但是,当我尝试这段代码时:
static void Main()
{
decimal bad = 2.718281828m;
NumberFixedPoint2 badNfp2 = (NumberFixedPoint2)bad;
Console.WriteLine(badNfp2);
}
Run Code Online (Sandbox Code Playgroud)
我很惊讶这个编译,并在运行时,写出来2.从int(值2)转换到NumberFixedPoint2这里很重要.(过载WriteLine,取入一个System.Decimal是优选的,在任何人的情况下奇观.)
为什么地球上的转换decimal是NumberFixedPoint2允许的?(顺便说一下,在上面的代码中,如果NumberFixedPoint2从结构变为类,则没有任何变化.)
您是否知道C#语言规范是否表示从int自定义类型的隐式转换"暗示"是否存在"直接"显式转换decimal为该自定义类型?
它变得更糟.请尝试使用此代码:
static void Main()
{
decimal? moreBad = 7.3890560989m;
NumberFixedPoint2? moreBadNfp2 = (NumberFixedPoint2?)moreBad;
Console.WriteLine(moreBadNfp2.Value);
}
Run Code Online (Sandbox Code Playgroud)
如您所见,我们Nullable<>在这里进行了(解除)转换.但是哦,是的,那确实可以编译.
在x86 "platform"中编译时,此代码会写出不可预测的数值.哪一个不时变化.举个例子,有一次我得到了2289956.现在,这是一个严重的错误!
当为x64平台编译时,上面的代码使用System.InvalidProgramException带有消息的公共语言运行时检测到无效程序而使应用程序崩溃.根据InvalidProgramException班级文件:
通常,这表示编译器中生成程序的错误.
有没有人(比如Eric Lippert,或曾经在C#编译器中解除过转换的人)知道这些错误的原因?比如,我们的代码中没有遇到它们的充分条件是什么?因为这种类型NumberFixedPoint2实际上是我们在实际代码中所拥有的东西(管理其他人的钱和东西).
Jon*_*eet 44
我只是回答问题的第一部分.(我建议第二部分应该是一个单独的问题;它更可能是一个错误.)
这里只有一个明确的从转换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)
Run Code Online (Sandbox Code Playgroud)
我相信这是符合规范的正确行为,即使它令人惊讶1.让我们按照C#4规范(用户定义的显式转换)的第6.4.5节进行操作.我不打算复制所有文本,因为这将是乏味的 - 只是在我们的案例中相关结果.同样我不打算使用下标,因为它们在这里与代码字体不兼容:)
S0和T0:S0是decimal,T0是NumberFixedPoint2.D从中可以考虑使用定义的转换运算符:just{ decimal, NumberFixedPoint2 }U.decimal 包括 int(参见第6.4.3节),因为有从一个标准的隐式转换int到decimal.因此,明确的转换操作符是在U和确实的唯一成员USx运营商中最具体的源类型U
S(decimal)转换,因此第一颗子弹出来了S(decimal包括int,而不是反过来)的类型转换,因此第二个子弹已经出来Sx是的int.Tx运营商中最具体的目标类型U
NumberFixedPoint2这样Tx的NumberFixedPoint2.U只包含一个运算符,它确实转换Sx为Tx,所以这是最具体的运算符S不是Sx,那么从一个标准的显式转换S来Sx执行.(所以这是decimal对int.)T是Tx因此没有必要在第三颗子弹的转换以粗体显示的行是确认标准显式转换确实可行的位,只实际指定了来自不同类型的显式转换.
1至少我发现它令人惊讶.我以前没有意识到这一点.
Ree*_*sey 24
您的第二部分(使用可空类型)似乎与当前编译器中的此已知错误非常相似. 根据Connect问题的回复:
虽然我们目前没有计划在下一版Visual Studio中解决此问题,但我们计划调查Roslyn中的修复程序
因此,有希望在Visual Studio和编译器的未来版本中纠正此错误.