关于c#:这个Nullable< T>的理由是什么?

What is the justification for this Nullable<T> behavior with implicit conversion operators

我在Nullable和隐式转换之间的交互中遇到了一些有趣的行为。我发现,如果从值类型为引用类型提供隐式转换,则当我希望出现编译错误时,它允许将Nullable类型传递给需要引用类型的函数。下面的代码演示了这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static void Main(string[] args)
{
    PrintCatAge(new Cat(13));
    PrintCatAge(12);
    int? cat = null;
    PrintCatAge(cat);
}

private static void PrintCatAge(Cat cat)
{
    if (cat == null)
        System.Console.WriteLine("What cat?");
    else
        System.Console.WriteLine("The cat's age is {0} years", cat.Age);
}

class Cat
{
    public int Age { get; set; }
    public Cat(int age)
    {
        Age = age;
    }

    public static implicit operator Cat(int i)
    {
        System.Console.WriteLine("Implicit conversion from" + i);
        return new Cat(i);
    }
}

输出:

1
2
3
4
The cat's age is 13 years
Implicit conversion from 12
The cat'
s age is 12 years
What cat?

如果从Cat中删除转换代码,则会得到预期的错误:


Error 3 The best overloaded method match for 'ConsoleApplication2.Program.PrintCatAge(ConsoleApplication2.Program.Cat)' has some invalid arguments


Error 4 Argument 1: cannot convert from 'int?' to 'ConsoleApplication2.Program.Cat

如果使用ilspy打开可执行文件,则生成的代码如下

1
2
int? num = null;
Program.PrintCatAge(num.HasValue ? num.GetValueOrDefault() : null);

在一个类似的实验中,我删除了转换并向PrintCatAge添加了一个重载,它需要一个int(不可以为空)来查看编译器是否执行类似的操作,但它不执行。

我理解正在发生的事情,但我不理解它的理由。这种行为对我来说是出乎意料的,看起来很奇怪。在转换文档或Nullable中,我没有在msdn上找到任何关于此行为的参考。

我提出的问题是,这是故意的吗?为什么会这样?


我之前说过(1)这是一个编译器错误,(2)这是一个新的错误。第一句话是准确的;第二句话是我匆忙赶往公共汽车时感到困惑。(我认为这对我来说是个新的bug是一个更复杂的bug,涉及提升转换和提升增量运算符。)

这是一个长期存在的已知编译器错误。乔恩·斯基特一段时间前第一次引起了我的注意,我相信在某个地方有一个堆积如山的问题,我不记得现在在哪里。或许是乔恩。

所以,这个bug。让我们定义一个"提升"操作符。如果一个运算符从不可为空的值类型s转换为不可为空的值类型t,那么还有一个"提升"运算符从s转换?到T?,这样一个空s?转换为空T?非空S?转换为T?通过展开S?到s,将s转换为t,并将t包装为t?.

该规范指出:(1)只有当s和t都是不可为空的值类型时,才存在提升运算符;(2)提升和未提升的转换运算符都被考虑是否适用于转换,如果两者都适用,则应用程序的源和目标类型。提升或未提升的合法转换用于确定所有适用转换的最佳源类型、最佳目标类型以及最终最佳转换。

不幸的是,实现完全违反了所有这些规则,并且这样做的方式是,如果不破坏现有的许多程序,我们就无法改变。

首先,我们违反了关于提升操作员存在的规则。如果s和t都是不可为空的值类型,或者s是不可为空的值类型,并且t是可以为其分配空值的任何类型,则实现将认为提升运算符存在:引用类型、可为空的值类型或指针类型。在所有这些情况下,我们产生一个提升操作员。

在您的特定情况下,我们将通过检查是否为空来将可为空的类型转换为引用类型cat来提升为可为空。如果源不为空,那么我们将正常转换;如果为空,则生成一个空cat。

第二,我们彻底违反了当某个候选对象是提升运算符时如何确定适用候选对象的最佳源和目标类型的规则,也违反了确定哪一个是最佳运算符的规则。

简而言之,这是一个巨大的混乱,不打破真正的客户是无法修复的,所以我们很可能会供奉在罗斯林的行为。我会考虑在某个时候在我的博客中记录编译器的确切行为,但是如果我是你的话,我不会在等待那一天的时候屏住呼吸。

当然,很多人也为错误道歉。