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

Почему короткие значения null преобразуются в значения int null для сравнения с null?

Когда я сравниваю короткие значения с нулевым значением, компилятор сначала преобразует их в целое число, чтобы сравнить с NULL. Например, рассмотрим этот простой код:

short? cTestA;
if (cTestA == null) { ... }

Он преобразуется компилятором в:

short? CS$0$0001 = cTestA;
int? CS$0$0002 = CS$0$0001.HasValue ? new int?(CS$0$0001.GetValueOrDefault()) : null;
if (!CS$0$0002.HasValue){ ... }

Это происходит для всех версий .NET, включая .NET 4.

Что мне здесь не хватает? В чем причина этого двойного преобразования только для проверки HasValue?

Последующие действия

Я ожидаю, что компилятор выполнит простую проверку с помощью .HasValue, if (cTestA.HasValue){}. По крайней мере, это то, что я делаю в своем коде после обнаружения этого преобразования.

Почему весь этот дополнительный код добавлен для такого простого теста?

4b9b3361

Ответ 1

Re: ваше последнее обновление:

Это ошибка в нулевом арифметическом оптимизаторе.

Оптимизатор с нулевым значением удалит ненужное преобразование в int?, когда вы сделаете что-то вроде:

short? s = null;
int? x = s + 1;

Неоптимизированный код имеет эквивалент:

short? s = null;
int? x;
int? temp = s.HasValue ? new int?((int)s.Value) : new int?();
x = temp.HasValue ? new int?(x.Value + 1) : new int?();

Оптимизированный код имеет эквивалент:

short? s = null;
int? x;
x = s.HasValue ? new int?((int)s.Value + 1) : new int?();

Однако оптимизатор содержит ошибку; мы не удаляем ненужное преобразование для равенства.

Спасибо, что привлек его к себе; мы исправим его для Roslyn. На самом деле я собираюсь написать оптимизатор с нулевым значением для Roslyn в ближайшие пару недель.

UPDATE: я написал этот оптимизатор, и если вас интересует, как это работает, я написал серию статей, которые начинаются здесь:

http://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/

Ответ 2

См. раздел 4.1.5 Спецификация языка С# 4.0. В частности, представляющие интерес:

С# поддерживает девять целых типов: sbyte, byte, short, ushort, int, uint, long, ulong и char. [опущенный текст]

Унарные и двоичные операторы интегрального типа всегда работают с 32-битная точность, неподписанная 32-битная точность, подписанная 64-разрядная точность или неподписанная 64-битная точность:

  • [опущенные маркеры]

  • Для двоичных файлов +, -, *,/,%, &, ^, |, ==,! =, > , <, >= и < знак равно операторы, операнды преобразуются в тип T, где T - первое int, uint, long и ulong, которые могут в полной мере представлять все возможные значения обоих операндов. Затем операцию выполняют с использованием точность типа T, а тип результата - T (или bool для реляционные операторы). Не допускается, чтобы один операнд тип long, а другой - типа ulong с бинарными операторами.

Операции с использованием short повышаются до int, и эти операции снимаются для их обнуляемых копий. (Это приводит к разделам 7.3.6.2 и 7.3.7)


Хорошо, это по дизайну, но по-прежнему не понимаю, почему они это делают, они оптимизируют строку, добавляет слишком много, поэтому оставляют только числа и добавляют больше кода для этого простого сравнения

Это просто способ разработки языка с учетом оптимизации в современной архитектуре. Не конкретно в этом контексте, но рассмотрите слова Эрика Липперта, как указано здесь

Арифметика никогда не выполняется в шортах на С#. Арифметика может выполняться в ints, uints, longs и ulongs, но арифметика никогда не делается в шортах. Шорты продвигают к int, и арифметика выполняется в ints, потому что, как я уже говорил, подавляющее большинство арифметических вычислений вписывается в int. Подавляющее большинство не вписывается в короткий. Короткая арифметика, возможно, медленнее на современном оборудовании, которое оптимизировано для ints, а короткая арифметика не занимает меньше места; это будет сделано в ints или longs на чипе.


Ваше последнее обновление:

То, что я ожидаю от компилятора, это сделать простую проверку с .HasValue if (cTestA.HasValue) {}, по крайней мере, это то, что я делаю в своем коде после того, как я обнаружил это преобразование. Так вот что я действительно не понимаю, почему бы не так просто подумать, но добавить весь этот дополнительный код. Компилятор всегда пытается оптимизировать код - почему здесь избежать этой простой проверки .HasValue. Я кое-что пропустил здесь...

Мне придется отложить до эксперта по компилятору, чтобы сказать, почему они решили перейти на конверсию вместо немедленной проверки HasValue, за исключением того, что может быть просто порядок операций. Спецификация языка говорит о том, что бинарные операнды операторов продвигаются и что они сделали в предоставленном фрагменте. Далее в описании языка указано, что проверки с помощью x == null, где x - тип значения NULL, могут быть преобразованы в !x.HasValue, и это также то, что они сделали. В скомпилированном коде, который вы представили, числовое продвижение просто превалировало над действием с нулевым значением.

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