通过编译器生成的局部变量隐式转换为具有可空结构的System.Double:为什么会失败? [英] Implicit conversion to System.Double with a nullable struct via compiler generated locals: why is this failing?

查看:63
本文介绍了通过编译器生成的局部变量隐式转换为具有可空结构的System.Double:为什么会失败?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

鉴于以下原因,为什么会引发InvalidCastException?我看不到它为什么应该在错误之外(这在x86中; x64在clrjit.dll中崩溃,并带有0xC0000005)。

Given the following, why does the InvalidCastException get thrown? I can't see why it should be outside of a bug (this is in x86; x64 crashes with a 0xC0000005 in clrjit.dll).

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()

.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 
}

不e在IL中的0x2D-0x3E行。它检索 MyDouble?实例,对其调用 GetValueOrDefault ,对该实例调用隐式运算符,然后包装结果在 Double?中,并将其存储在编译器生成的 CS $ 0 $ 0001 本地中。在0x3F到0x55行中,我们检索 CS $ 0 $ 0001 值,通过 GetValueOrDefault 'www'进行比较,然后将其与0进行比较... 但请稍等!在行0x46上对 MyDouble :: op_Implicit 的额外调用是什么?

Note lines 0x2D - 0x3E in the IL. It retrieves the MyDouble? instance, calls GetValueOrDefault on it, calls the implicit operator on that, and then wraps the result in a Double? and stores it in the compiler-generated CS$0$0001 local. In lines 0x3F to 0x55, we retrieve the CS$0$0001 value, 'unwrap' via GetValueOrDefault and then compare to 0... BUT WAIT A MINUTE! What is that extra call to MyDouble::op_Implicit doing on line 0x46?

如果我们调试C#程序,我们确实会看到2次调用隐式运算符Double(MyDouble value),并且这是第二次调用失败,因为 value 未初始化。

If we debug the C# program, we indeed see 2 calls to implicit operator Double(MyDouble value), and it is the 2nd call that fails, since value is not initialized.

这是怎么回事?

推荐答案

这显然是C#编译器错误。

It is clearly a C# compiler bug. Thanks for bringing it to my attention.

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

Incidentally, it is a bad practice to have a user defined implicit conversion operator that throws an exception; the documentation states that implicit conversions should be those that never throw. Are you sure you don't want this to be an explicit conversion?

无论如何,回到错误中。

Anyway, back to the bug.

C#3和4中的错误再现,但C#2中没有。这意味着这是我的错。当我重命名用户定义的提升的隐式运算符代码以使其与表达式树lambda一起使用时,可能是导致该错误的原因。对于那个很抱歉!该代码非常棘手,显然我没有对其进行足够的测试。

The bug repros in C# 3 and 4 but not in C# 2. Which means that it was my fault. I probably caused the bug when I redid the user-defined lifted implicit operator code in order to make it work with expression tree lambdas. Sorry about that! That code is very tricky, and apparently I did not test it adequately.

该代码应该执行的操作是:

What the code is supposed to do is:

首先,重载解决方案尝试解析==的含义。对于两个参数均有效的最佳==运算符是将两个可为null的双精度数进行比较的提升运算符。因此,应将其分析为:

First, overload resolution attempts to resolve the meaning of ==. The best == operator for which both arguments are valid is the lifted operator that compares two nullable doubles. Therefore it should be analyzed as:

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

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

(If you write the code like this then it does the right thing in C# 3 and 4.)

解除==运算符的含义是:

The meaning of the lifted == operator is:


  • 同时评估两个参数

  • 如果两个均为空,则结果为true;在这种情况下,显然不会发生这种情况

  • ,如果一个为null,而另一个为非结果为假

  • 如果两者都不为null,则将它们都展开为双精度并比较为双精度。

  • evaluate both arguments
  • if both are null then the result is true -- clearly this cannot happen in this case
  • if one is null and the other is not then the result is false
  • if both are not null then both are unwrapped to double and compared as doubles.

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

Now the question is "what is the right way to evaluate the left hand side?"

我们在这里从MyDouble得到了一个提升的用户定义转换运算符?加倍?正确的行为是:

We have here a lifted user-defined conversion operator from MyDouble? to double?. The correct behaviour is:


  • 如果 my为null,则结果为null double?。

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

在此过程中显然出了点问题。

Clearly something is going wrong in this process.

我将在数据库中输入一个错误,但可能会进行任何修复错过了将其纳入下一个Service Pack的更改截止日期。如果您是我,我会研究解决方法。再次为该错误表示歉意。

I'll enter a bug in our database, but any fix will probably miss the deadline for changes that make it into the next service pack. I would be looking into workarounds if I were you. Again, apologies for the error.

这篇关于通过编译器生成的局部变量隐式转换为具有可空结构的System.Double:为什么会失败?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆