Кажется, что некоторые части выражения могут быть оценены во время компиляции, тогда как другие части во время выполнения

Наверное, глупый вопрос, так как я, возможно, уже ответил на мой вопрос, но я просто хочу быть уверенным, что я ничего не пропущу

Константные выражения оцениваются во время компиляции в рамках проверенного контекста. Я думал, что следующее выражение не должно оцениваться во время компиляции, так как я предполагал, что С# считает конкретное выражение постоянным выражением только в том случае, если все операнды в левой части являются константами:

int i= 100;
long u = (int.MaxValue  + 100 + i); //error

Вместо этого появляется компилятор, рассматривающий любое подвыражение, в котором оба операнда являются константами как константное выражение, даже если другие операнды в выражении не являются константами? Таким образом, компилятор может оценивать только часть выражения во время компиляции, в то время как оставшееся выражение (которое содержит непостоянные значения) будет оцениваться во время выполнения → Я предполагаю, что в следующем примере оценивается только (200 +100) во время компиляции

int i=100;
long l = int.MaxValue  + i + ( 200 + 100 ); // works

Правильны ли мои предположения?

спасибо

Ответы

Ответ 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 на самом деле достаточно утончен в распознавании различных шаблонов для склеивания строк во время компиляции.

Мы могли бы сделать то же самое для непроверенной арифметики. Мы просто не обошли его.