隐式转换运算符对此Nullable <T>行为的理由是什么?

Tho*_*mas 32 .net c# nullable implicit-conversion

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

The cat's age is 13 years
Implicit conversion from 12
The cat's age is 12 years
What cat?
Run Code Online (Sandbox Code Playgroud)

如果从中删除转换代码,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打开可执行文件,则生成的代码如下所示

int? num = null;
Program.PrintCatAge(num.HasValue ? num.GetValueOrDefault() : null);
Run Code Online (Sandbox Code Playgroud)

在类似的实验中,我删除了转换并添加了一个重载,PrintCatAge它接受一个int(不可为空)来查看编译器是否会执行类似的操作,但事实并非如此.

我理解发生了什么,但我不明白它的理由.这种行为对我来说意外,而且看起来很奇怪.我没有成功在转换文档中找到对MSDN上此行为的任何引用Nullable<T>.

我提出的问题是,这是故意的,并且有解释为什么会发生这种情况?

Eri*_*ert 29

我之前说过(1)这是一个编译器错误,(2)它是一个新的错误.第一个陈述是准确的; 第二个是我急于赶紧赶上公共汽车.(我想到的这个对我来说很新的错误是一个更复杂的错误,涉及提升转换和提升增量运算符.)

这是一个长期存在的已知编译器错误.很久以前,Jon Skeet首先引起了我的注意,我相信在某个地方有一个关于它的StackOverflow问题; 我不记得哪个随便.乔恩也许.

所以,这个bug.让我们定义一个"提升"运算符.如果运算符从非可空值类型S转换为非可空值类型T,那么还有一个"提升"运算符从S?到T ?,这样一个空S?转换为空T?和一个非空S?转换为T?打开S?到S,将S转换为T,并将T包装到T?.

规范说明(1)存在提升算子的唯一情况是当S和T都是非可空值类型时,(2)提升和非提升转换算子被认为是否是它们是适用的转化候选者,如果适用,则适用转化的来源和目标类型(已取消或未取消)用于确定最佳来源类型,最佳目标类型,并最终转换所有适用的转化次数.

不幸的是,实施完全违反了所有这些规则,并且这样做的方式是我们不能在不破坏许多现有程序的情况下进行更改.

首先,我们违反了关于解除运营商存在的规则.如果S和T都是非可空值类型,或者如果S是非可空值类型,并且T是可以赋值为null的任何类型,则实现认为提升运算符存在:引用类型,可空值类型或指针类型.在所有这些情况下,我们生产一个提升的操作员.

在您的特定情况下,我们通过检查null来将可空类型转换为引用类型Cat,从而提升为可空.如果源不为null,那么我们正常转换; 如果是,那么我们产生一个空猫.

其次,我们违反了关于如何确定适用候选人的最佳来源和目标类型的规则,当其中一个候选人是解除运营商时,我们也违反了确定哪个是最佳运营商的规则.

简而言之,这是一个很大的混乱,如果不打破真正的客户就无法修复,因此我们很可能会将这种行为归于罗斯林.我会考虑在某些时候在我的博客中记录编译器的确切行为,但是如果我是你,那么在等待那一天我不会屏住呼吸.

当然,许多人为错误道歉.

  • 我以为只有Jon Skeet在通勤时回答了问题。 (2认同)
  • @Thomas:不客气.在搜索Web或规范时要使用的关键字是"C#提升的可空用户定义的转换运算符". (2认同)