鉴于以下内容,为什么会抛出InvalidCastException?我不明白为什么它应该在一个bug之外(这是在x86; x64与clrjit.dll中的0xC0000005崩溃)。
class Program
{
static void Main(string[] args)
{
MyDouble? my = new MyDouble(1.0);
Boolean compare = my == 0.0;
}
struct MyDouble
{
Double? _value;
public MyDouble(Double value)
{
_value = value;
}
public static implicit operator Double(MyDouble value)
{
if (value._value.HasValue)
{
return value._value.Value;
}
throw new InvalidCastException("MyDouble value cannot convert to System.Double: no value present.");
}
}
}
以下是为Main()
生成的CIL:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 3
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<valuetype Program/MyDouble> my,
[1] bool compare,
[2] valuetype [mscorlib]System.Nullable`1<valuetype Program/MyDouble> CS$0$0000,
[3] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0001)
L_0000: nop
L_0001: ldloca.s my
L_0003: ldc.r8 1
L_000c: newobj instance void Program/MyDouble::.ctor(float64)
L_0011: call instance void [mscorlib]System.Nullable`1<valuetype Program/MyDouble>::.ctor(!0)
L_0016: nop
L_0017: ldloc.0
L_0018: stloc.2
L_0019: ldloca.s CS$0$0000
L_001b: call instance bool [mscorlib]System.Nullable`1<valuetype Program/MyDouble>::get_HasValue()
L_0020: brtrue.s L_002d
L_0022: ldloca.s CS$0$0001
L_0024: initobj [mscorlib]System.Nullable`1<float64>
L_002a: ldloc.3
L_002b: br.s L_003e
L_002d: ldloca.s CS$0$0000
L_002f: call instance !0 [mscorlib]System.Nullable`1<valuetype Program/MyDouble>::GetValueOrDefault()
L_0034: call float64 Program/MyDouble::op_Implicit(valuetype Program/MyDouble)
L_0039: newobj instance void [mscorlib]System.Nullable`1<float64>::.ctor(!0)
L_003e: stloc.3
L_003f: ldloca.s CS$0$0001
L_0041: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_0046: call float64 Program/MyDouble::op_Implicit(valuetype Program/MyDouble)
L_004b: conv.r8
L_004c: ldc.r8 0
L_0055: bne.un.s L_0060
L_0057: ldloca.s CS$0$0001
L_0059: call instance bool [mscorlib]System.Nullable`1<float64>::get_HasValue()
L_005e: br.s L_0061
L_0060: ldc.i4.0
L_0061: stloc.1
L_0062: ret
}
注意IL中的0x2D - 0x3E行。它检索MyDouble?
实例,在其上调用GetValueOrDefault
,在其上调用隐式运算符,然后将结果包装在Double?
中并将其存储在编译器生成的CS$0$0001
中本地的。在行0x3F到0x55中,我们检索CS$0$0001
值,'解包'通过GetValueOrDefault
然后比较为0 ... 但等待一分钟!在{0}行上对MyDouble::op_Implicit
执行的额外调用是什么?
如果我们调试C#程序,我们确实会看到2次调用implicit operator Double(MyDouble value)
,而第2次调用失败,因为value
未初始化。
这里发生了什么?
答案 0 :(得分:42)
这显然是一个C#编译器错误。谢谢你引起我的注意。
顺便说一下,让用户定义的隐式转换运算符抛出异常是一种不好的做法。文档声明隐式转换应该是那些从不抛出的转换。你确定你不希望这是一个明确的转换吗?
无论如何,回到bug。
C#3和4中的错误重现,但不是C#2中的错误。这意味着这是我的错。当我重新编写用户定义的提升隐式操作符代码以使其与表达式树lambdas一起工作时,我可能导致了这个错误。对于那个很抱歉!该代码非常棘手,显然我没有充分测试它。
代码应该做的是:
首先,重载解析试图解决==的含义。两个参数都有效的最佳==运算符是比较两个可空双精度的提升运算符。因此,应将其分析为:
Boolean compare = (double?)my == (double?)0.0;
(如果您编写这样的代码,那么它在C#3和4中做了正确的事。)
提升的==运算符的含义是:
现在的问题是“评估左手边的正确方法是什么?”
我们在这里有一个来自MyDouble的提升用户定义转换运营商?加倍?正确的行为是:
显然在这个过程中出现了问题。
我将在我们的数据库中输入一个错误,但任何修复都可能会错过进入下一个服务包的更改截止日期。如果我是你,我会寻找解决方法。再次,为错误道歉。
答案 1 :(得分:6)
这肯定对我来说是一个编译器错误。 IL建议编译器生成转换MyDouble的代码?转换运算符为double,然后为double?但是,当它再次使用转换运算符时,需要一个急转直下?那是坏的,错误的论证类型。也没有必要。
这个feedback article类似于这个错误。已经超过6年,这必定是编译器的一个棘手的部分。我确实想象它。