Подтвердить что ты не робот

Каково обоснование этого поведения Nullable <T> с неявными операторами преобразования

Я обнаружил некоторое интересное поведение во взаимодействии между 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 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 age is 13 years
Implicit conversion from 12
The cat 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), чтобы увидеть, выполнит ли компилятор аналогичную операцию, но это не так.

Я понимаю, что происходит, но я не понимаю его оправдания. Такое поведение неожиданно для меня и кажется странным. Мне не удалось найти какую-либо ссылку на это поведение в MSDN в документации для конверсий или Nullable<T>.

Затем я задаю вопрос: является ли это преднамеренным и есть ли объяснение, почему это происходит?

4b9b3361

Ответ 1

Я сказал ранее, что (1) это ошибка компилятора, и (2) она новая. Первое утверждение было точным; во-вторых, я смутился в своей поспешности, чтобы добраться до автобуса вовремя. (Ошибка, о которой я думал, это для меня новичок, это гораздо более сложная ошибка, связанная с отмененными преобразованиями и отмененными операторами приращения.)

Это известная ошибка компилятора. Джон Скит впервые привлек мое внимание некоторое время назад, и я считаю, что там есть вопрос StackOverflow об этом; Я не помню, где небрежно. Возможно, Джон делает.

Итак, ошибка. Пусть определим "поднятый" оператор. Если оператор преобразует из значения типа N с нулевыми значениями в тип N с непустым значением T, то существует также "поднятый" оператор, который преобразует из S? к T?, так что нулевой S? преобразуется в нулевой T? и непустой S? преобразуется в T? путем разворачивания S? на S, преобразование S в T и перенос T в T?.

В спецификации указано, что (1) единственная ситуация, в которой есть поднятый оператор, - это когда S и T являются нечетными типами значений, и (2) что поднятые и не поднятые операторы преобразования считаются для того, являются ли они применимыми кандидатами для преобразования, и, если они применимы, то исходный и целевой типы применимых преобразований, снятые или отмененные, используются для определения наилучшего типа источника, наилучшего целевого типа и, в конечном счете, наилучшего преобразования всех применимые преобразования.

К сожалению, реализация полностью нарушает все эти правила и делает это так, что мы не можем изменить, не нарушая многие существующие программы.

Во-первых, мы нарушаем правило о существовании поднятых операторов. Поднятый оператор рассматривается реализацией, если S и T являются нечетными типами значений, или если S - тип значения, который не является нулевым, и T - любой тип, которому может быть присвоен нуль: тип ссылки, значение NULL тип или тип указателя. Во всех этих случаях мы производим поднятый оператор.

В вашем конкретном случае мы поднимаем до нулевого значения, говоря, что мы конвертируем тип с нулевым значением в ссылочный тип Cat, проверяя значение null. Если источник не равен нулю, мы обычно конвертируем; если это так, то мы создаем нулевую кошку.

Во-вторых, мы тщательно нарушаем правило о том, как определить наилучшие исходные и целевые типы применимых кандидатов, когда один из этих кандидатов является поднятым оператором, а также нарушаем правила определения того, какой из них является лучшим оператором.

Короче говоря, это большой беспорядок, который не может быть исправлен без нарушения реальных клиентов, и поэтому мы, скорее всего, закрепим поведение в Roslyn. В какой-то момент я буду рассматривать документацию о точном поведении компилятора в своем блоге, но я не задерживал бы дыхание, ожидая этого дня, если бы был вами.

И, конечно, многие извинения за ошибки.