Я все еще нахожу этот вопрос немного запутанным, но позвольте мне посмотреть, могу ли я перефразировать вопрос в форму, на которую я могу ответить. Во-первых, позвольте мне перефразировать фон вопроса:
В C # 2.0 этот код:
int x = 123;
int y;
if (x * 0 == 0)
y = 345;
Console.WriteLine(y);
трактовался так, как если бы вы написали
int x = 123;
int y;
if (true)
y = 345;
Console.WriteLine(y);
, который в свою очередь рассматривается как:
int x = 123;
int y;
y = 345;
Console.WriteLine(y);
Какая легальная программа.
Но в C # 3.0 мы приняли решающее изменение, чтобы предотвратить это. Компилятор больше не рассматривает условие как «всегда истинное», несмотря на то, что мы оба знаем, что оно всегда верно. Теперь мы делаем это недопустимой программой, потому что компилятор считает, что он не знает , что тело «if» всегда выполняется, и поэтому не знает, что перед ним всегда назначается локальная переменная y используется.
Почему корректно поведение C # 3.0?
Это правильно, потому что в спецификации говорится:
константное выражение должно содержать только константы. x * 0 == 0
не является константным выражением, поскольку оно содержит непостоянный член, x
.
известно, что следствие if
всегда достижимо, только если условие является константным выражением, равным true
.
Следовательно, данный код не должен классифицировать последствия того, что условный оператор будет всегда доступен, и, следовательно, не должен классифицировать локальный y
как однозначно назначенный.
Почему желательно, чтобы константное выражение содержало только константы?
Мы хотим, чтобы язык C # был понятным для его пользователей и корректно реализовывался авторами компиляторов. Требование, чтобы компилятор делал все возможные логические выводы о значениях выражений, работает против этих целей. Должно быть simple , чтобы определить, является ли данное выражение константой, и если да, каково его значение. Проще говоря, код константной оценки должен знать как выполнять арифметику, но не должен знать факты об арифметических манипуляциях. Оценщик констант знает как умножить 2 * 1, но ему не нужно знать факт , что «1 - это мультипликативная единица на целых числах».
Теперь, возможно, писатель компилятора может решить , что существуют области, в которых они могут быть умными, и тем самым сгенерировать более оптимальный код. Авторам компиляторов разрешено делать это, но не таким образом, чтобы изменять, является ли код легальным или недопустимым . Им разрешено выполнять оптимизацию только для того, чтобы улучшал вывод компилятора, если ему присвоен правильный код .
Как произошла ошибка в C # 2.0?
Произошло то, что компилятор был написан для слишком раннего запуска арифметического оптимизатора. Оптимизатор - это бит, который должен быть умным, и он должен был запустить после , если программа была определена как допустимая. Она работала до , программа была признана законной, и, следовательно, влияла на результат.
Это было потенциальное критическое изменение: хотя оно и привело компилятор в соответствие со спецификацией, оно также потенциально превратило рабочий код в код ошибки. Что послужило причиной изменения?
Функции LINQ и, в частности, деревья выражений. Если вы сказали что-то вроде:
(int x)=>x * 0 == 0
и преобразовал это в дерево выражений, ожидаете ли вы, что оно сгенерирует дерево выражений для
(int x)=>true
? Возможно нет! Вы, вероятно, ожидали, что он создаст дерево выражений для «умножения х на ноль и сравнения результата с нолем». Деревья выражений должны сохранять логическую структуру выражения в теле.
Когда я писал код дерева выражений, еще не было ясно, собирается ли проектная комиссия решать,
()=>2 + 3
собирался сгенерировать дерево выражений для «добавить два-три» или дерево выражений для «пять».Мы определились с последним - константы складываются перед созданием деревьев выражений, но арифметика не должна проходить через оптимизатор до генерации деревьев выражений.
Итакдавайте теперь рассмотрим зависимости, которые мы только что указали:
- Арифметическая оптимизация должна произойти до Codegen.
- Переписывание дерева выражений должно произойти до арифметической оптимизации
- Свертывание констант должно произойти до переписывания дерева выражений
- Свертывание констант должно произойти до анализа потока
- Анализ потока должен произойти до переписывания дерева выражений (потому что нам нужно знать, использует ли дерево выраженийнеинициализированный локальный)
Мы должны найти приказ выполнить всю эту работу, которая учитывает все эти зависимости.Компилятор в C # 2.0 делал их в следующем порядке:
- постоянное свертывание и арифметическая оптимизация одновременно
- анализ потока
- codegen
Куда может пойти переписывание дерева выражений?Нигде!И очевидно, что это ошибка, потому что анализ потока теперь учитывает факты, полученные арифметическим оптимизатором.Мы решили переработать компилятор так, чтобы он делал вещи в следующем порядке:
- постоянное свертывание
- анализ потока
- перезапись дерева выражений
- арифметикаоптимизация
- codegen
Что, очевидно, требует критического изменения.
Теперь я решил сохранить существующее нарушенное поведение, выполнив следующее:
- постоянное свертывание
- арифметическая оптимизация
- анализ потока
- арифметическая де-оптимизация
- перезапись дерева выражений
- повторная оптимизация арифметики
- codegen
Где оптимизированное арифметическое выражение будет содержать указатель на его неоптимизированную форму.Мы решили, что это слишком сложная задача, чтобы сохранил ошибку .Мы решили, что вместо было бы лучше исправить ошибку , принять решающее изменение и сделать архитектуру компилятора более понятной.