27

鉴于以下情况,为什么会抛出 InvalidCastException?我不明白为什么它应该在错误之外(这是在 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.");
        }
    }
}

这是为 生成的 CIL Main()

.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它,调用它的隐式运算符,然后将结果包装在 a 中Double?并将其存储在编译器生成的CS$0$0001本地中。在 0x3F 到 0x55 行中,我们CS$0$0001通过“unwrap”检索值,GetValueOrDefault然后与 0 进行比较……请稍等!MyDouble::op_Implicit0x46 行的额外调用是什么?

如果我们调试 C# 程序,我们确实会看到 2 次调用implicit operator Double(MyDouble value),并且是第二次调用失败,因为value没有初始化。

这里发生了什么?

4

2 回答 2

44

这显然是一个 C# 编译器错误。感谢您引起我的注意。

顺便说一句,让用户定义的隐式转换运算符引发异常是一种不好的做法。该文档指出,隐式转换应该是那些永远不会抛出的转换。您确定不希望这是显式转换吗?

无论如何,回到错误。

C# 3 和 4 中的错误重现,但 C# 2 中没有。这意味着这是我的错。当我重新编写用户定义的提升隐式运算符代码以使其与表达式树 lambda 一起工作时,我可能会导致该错误。对于那个很抱歉!该代码非常棘手,显然我没有充分测试它。

代码应该做的是:

首先,重载解析尝试解析 == 的含义。两个参数都有效的最佳 == 运算符是比较两个可为空的双精度值的提升运算符。因此应分析为:

Boolean compare = (double?)my == (double?)0.0; 

(如果您编写这样的代码,那么它在 C# 3 和 4 中是正确的。)

提升 == 运算符的含义是:

  • 评估两个论点
  • 如果两者都为空,则结果为真——显然在这种情况下这不会发生
  • 如果一个为空而另一个不是,则结果为假
  • 如果两者都不为空,则将两者都展开为双精度并作为双精度进行比较。

现在的问题是“评估左侧的正确方法是什么?”

我们这里有一个来自 MyDouble 的提升的用户定义转换运算符?翻倍?正确的行为是:

  • 如果 "my" 为 null,则结果为 null double?。
  • 如果“my”不为空,则结果是用户定义的 my.Value 到 double 的转换,然后将该 double 转换为 double?。

显然,在这个过程中出现了问题。

我将在我们的数据库中输入一个错误,但任何修复都可能会错过将其纳入下一个服务包的更改的截止日期。如果我是你,我会寻找解决方法。再次为错误道歉。

于 2011-03-04T16:40:01.000 回答
6

这对我来说肯定看起来像一个编译器错误。IL 建议编译器正在生成代码以转换 MyDouble? 使用转换运算符转换为双精度,然后转换为双精度?但是当它再次在那个双精度上使用转换运算符时会急剧下降?那很糟糕,错误的参数类型。也没有必要。

反馈文章类似于此错误。已经超过 6 年了,这一定是编译器的一个棘手部分。我想是的。

于 2011-03-04T02:31:08.230 回答