33

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(不可为空)来查看编译器是否会执行类似的操作,但事实并非如此。

我明白发生了什么,但我不明白这样做的理由。这种行为对我来说是出乎意料的,而且看起来很奇怪。我在 MSDN 上的转换文档或Nullable<T>.

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

4

1 回答 1

29

我之前说过(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 将可空类型转换为引用类型 Cat 来提升为可空。如果源不为空,那么我们正常转换;如果是,那么我们生成一个空 Cat。

其次,我们彻底违反了当其中一个候选者是提升运算符时如何确定适用候选者的最佳源和目标类型的规则,并且我们也违反了确定哪个是最佳运算符的规则。

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

当然,许多人为这些错误道歉。

于 2012-04-16T23:24:57.670 回答