使用隐式转换运算符的这种 Nullable 行为的理由是什么?

本文关键字:是什么 理由 转换 运算符 Nullable | 更新日期: 2023-09-27 18:31:15

我在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);
    }
}

输出:

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 打开可执行文件,生成的代码如下所示

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

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

我明白正在发生的事情,但我不明白这样做的理由。这种行为对我来说是出乎意料的,看起来很奇怪。我在转换或Nullable<T>文档中没有成功地在 MSDN 上找到对此行为的任何引用。

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

使用隐式转换运算符的这种 Nullable<T> 行为的理由是什么?

我之前说过(1)这是一个编译器错误,(2)它是一个新的错误。第一种说法是准确的;第二个是我对准时上车的匆忙感到困惑。(我想到的对我来说是新的错误是一个更复杂的错误,涉及提升的转换和提升的增量运算符。

这是一个长期存在的已知编译器错误。Jon Skeet前段时间第一次引起我的注意,我相信在某个地方有一个关于它的StackOverflow问题;我不记得在哪里随手。也许乔恩有。

所以,错误。让我们定义一个"提升"运算符。如果运算符从不可为空的值类型 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 来提升为可为空。如果源不为空,则我们正常转换;如果是,那么我们生成一个空猫。

其次,当其中一个候选者是

解除运算符时,我们完全违反了有关如何确定适用候选对象的最佳来源和目标类型的规则,并且我们还违反了有关确定哪个是最佳运算符的规则。

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

当然,为错误道歉。