Ответ 1
doec С# классифицирует любое подвыражение, в котором оба операнда являются константами как константное выражение, даже если другие операнды в выражении не являются константами?
Ответ на ваш вопрос - "да", но важно четко понимать, что представляет собой "подвыражение".
Я предполагаю, что в "int.MaxValue + я + (200 + 100)" только (200 + 100) оценивается во время компиляции
Правильно. Теперь, если бы вы сказали вместо "int.MaxValue + я + 200 + 100", то "200 + 100" никогда не были бы оценены вообще, потому что это не подвыражение из-за ассоциативности.
Это немного тонко. Позвольте мне подробно объяснить.
Прежде всего, давайте различать константы времени компиляции de jure и константы времени компиляции де-факто. Позвольте привести пример. Рассмотрим это тело метода:
const int x = 123;
int y = x + x;
if (y * 0 != 0) Console.WriteLine("WOO HOO");
M(ref y);
В С# 1 и 2, если вы скомпилировали это с оптимизацией, это было бы скомпилировано так, как если бы вы написали:
int y = 246;
M(ref y);
Постоянная x обращается в нуль, y инициализируется константным сложенным выражением, а арифметический оптимизатор понимает, что любое локальное целое число, равное нулю, никогда не равно нулю, поэтому оно также оптимизирует это.
В С# 3 я случайно представил ошибку в оптимизаторе, которая также не была зафиксирована в С# 4. В С# 3/4 мы будем генерировать это как
int y = 246;
bool b = false;
if (b) Console.WriteLine("WOO HOO");
M(ref y);
То есть, арифметика оптимизирована, но мы не продвигаемся дальше и не оптимизируем "if (false)".
Разница в том, что постоянное поведение сгибания на x + x гарантируется во время компиляции, но постоянное поведение сгибания на частично-переменном выражении y * 0 не является.
Я сожалею об ошибке и извиняюсь. Однако причина, по которой я изменил это в С# 3 и случайно ввел ошибку в генераторе кода, - это исправить ошибку в семантическом анализаторе. В С# 2.0 это было законно:
int z;
int y = 246;
if (y * 0 == 0) z = 123;
Console.WriteLine(z);
Это не должно быть законным. Вы знаете, и я знаю, что y * 0 == 0 всегда будет истинным, и поэтому z назначается перед чтением, но спецификация говорит, что этот анализ должен выполняться только в том случае, если выражение в "if" является компиляцией, константу времени, а не константу времени компиляции, поскольку она содержит переменную. Мы взяли нарушение для С# 3.0.
Итак, давайте предположим, что вы понимаете разницу между константой де-юре, которую нужно оценить, потому что спецификация говорит об этом, и константу де-факто, которая оценивается, потому что оптимизатор является умным. Ваш вопрос заключается в том, при каких обстоятельствах выражение может быть де-юре и де-факто частично "свернуто" во время компиляции? ( "Сложенным" я имею в виду разрешение выражения, содержащего константы, в более простое выражение.)
Первое, что мы должны рассмотреть, это ассоциативность операторов. Только после того, как мы провели анализ ассоциативности и приоритетности, знаем ли мы, что есть и не является подвыражением. Рассмотрим
int y = 3;
int x = 2 - 1 + y;
Сложение и вычитание являются лево-ассоциативными, как и большинство операторов в С#, так что это то же самое, что
int x = (2 - 1) + y;
Теперь ясно (2 - 1) является постоянным выражением де-юре, поэтому оно свернуто и становится равным 1.
Если, с другой стороны, вы сказали
int x = y + 2 - 1;
Это
int x = (y + 2) - 1;
и он не сложен, потому что это два непостоянных выражения.
Это может иметь реальный эффект в проверенном контексте. Если y - int.MaxValue - 1, то первая версия не будет переполняться, а вторая версия будет!
Теперь компилятору и оптимизатору разрешено говорить "хорошо, я знаю, что это неконтролируемый контекст, и я знаю, что могу превратить это в" y + 1 "безопасно, поэтому буду". Компилятор С# не делает этого, но дрожит. В вашем конкретном примере
long l = int.MaxValue + i + 200 + 100 ;
на самом деле является code-gen'd компилятором С# как
long l = ((int.MaxValue + i) + 200) + 100 ;
И не
long l = (int.MaxValue + i) + 300;
Дрожание может решить сделать эту оптимизацию, если она того пожелает, и может доказать, что это безопасно.
Но
long l = int.MaxValue + i + (200 + 100);
естественно, будет сгенерирован как
long l = (int.MaxValue + i) + 300;
Однако мы выполняем оптимизацию, которую вы хотите использовать для строк в компиляторе С#! Если вы скажете
string y = whatever;
string x = y + "A" + "B" + "C";
то вы можете подумать, что лево-ассоциативное выражение так:
string x = ((y + "A") + "B") + "C";
И поэтому не будет постоянной сгибания. Однако мы фактически обнаруживаем эту ситуацию и во время компиляции делаем сгибание вправо, поэтому мы генерируем это, как если бы вы написали
string x = y + "ABC";
Таким образом, экономия затрат на конкатенации во время выполнения. Оптимизатор строк concat на самом деле достаточно утончен в распознавании различных шаблонов для склеивания строк во время компиляции.
Мы могли бы сделать то же самое для непроверенной арифметики. Мы просто не обошли его.