Спасибо всем, кто внес вклад в анализ этой проблемы.Это явно ошибка компилятора.Похоже, что это происходит только тогда, когда есть отмененное преобразование, включающее два обнуляемых типа в левой части оператора объединения.
Я еще не определил, где именно что-то пошло не так, но в какой-то момент во время "«Обнуляемое понижение» фаза компиляции - после первоначального анализа, но до генерации кода - мы уменьшаем выражение
result = Foo() ?? y;
из приведенного выше примера до морального эквивалента:
A? temp = Foo();
result = temp.HasValue ?
new int?(A.op_implicit(Foo().Value)) :
y;
Ясноэто неверно;правильное понижение составляет
result = temp.HasValue ?
new int?(A.op_implicit(temp.Value)) :
y;
Насколько я могу судить, исходя из моего анализа на данный момент, здесь можно предположить, что обнуляемый оптимизатор сходит с рельсов.У нас есть обнуляемый оптимизатор, который ищет ситуации, когда мы знаем, что определенное выражение типа обнуляемого не может быть нулевым.Рассмотрим следующий наивный анализ: мы можем сначала сказать, что
result = Foo() ?? y;
совпадает с
A? temp = Foo();
result = temp.HasValue ?
(int?) temp :
y;
, а затем мы можем сказать, что
conversionResult = (int?) temp
- этото же самое, что и
A? temp2 = temp;
conversionResult = temp2.HasValue ?
new int?(op_Implicit(temp2.Value)) :
(int?) null
Но оптимизатор может вмешаться и сказать: «Стоп, подожди минутку, мы уже проверили, что temp не нуль; нет необходимости проверять его на ноль во второй раз только потому, что мывызов оператора отмененного преобразования ".Мы бы оптимизировали его до
new int?(op_Implicit(temp2.Value))
Я предполагаю, что мы где-то кешируем тот факт, что оптимизированная форма (int?)Foo()
равна new int?(op_implicit(Foo().Value))
, но на самом деле это не оптимизированная форма, которую мы хотим;нам нужна оптимизированная форма Foo () - заменено на временное и затем преобразовано.
Многие ошибки в компиляторе C # являются результатом неправильных решений кэширования.Слово мудрому: каждый раз, когда вы кешируете факт для последующего использования, вы потенциально создаете несоответствие, если что-то значимое изменится .В этом случае важная вещь, которая изменилась после первоначального анализа, заключается в том, что вызов Foo () всегда должен быть реализован как выборка временного.
Мы провели большую реорганизацию прохода перезаписываемой переменной в C #3.0.Ошибка воспроизводится в C # 3.0 и 4.0, но не в C # 2.0, что означает, что ошибка, вероятно, была моей.Извините!
Я получу сообщение об ошибке в базе данных, и мы посмотрим, сможем ли мы исправить это для будущей версии языка.Еще раз спасибо всем за ваш анализ;это было очень полезно!
ОБНОВЛЕНИЕ: я переписал обнуляемый оптимизатор с нуля для Roslyn;теперь он работает лучше и избегает таких странных ошибок.Некоторые мысли о том, как работает оптимизатор в Roslyn, см. В моей серии статей, которая начинается здесь: https://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/