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

Строковые интерполяции

Я пытаюсь понять, почему мой unit test терпит неудачу (третий пример ниже):

var date = new DateTime(2017, 1, 1, 1, 0, 0);

var formatted = "{countdown|" + date.ToString("o") + "}";

//Works
Assert.AreEqual(date.ToString("o"), $"{date:o}");
//Works
Assert.AreEqual(formatted, $"{{countdown|{date.ToString("o")}}}");
//This one fails
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

AFAIK, это должно работать правильно, но кажется, что он не передает параметр форматирования правильно, он отображается как код {countdown|o}. Любая идея, почему это не удается?

4b9b3361

Ответ 1

Проблема с этой линией

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

заключается в том, что у вас есть 3 фигурных кавычки после format string переменной, которая должна быть экранирована, и она начинает экранировать слева направо, поэтому она обрабатывает первые 2 фигурных кавычки как часть строки формата и третьей фигурной кавычки как закрывающий.

Поэтому он преобразует o в o} и не может его интерполировать.

Это должно работать

Assert.AreEqual(formatted, $"{{countdown|{date:o}"+"}");

Обратите внимание, что более простой $"{date}}}" (т.е. 3 завитки после переменной без format string) работает, потому что он распознает, что первая фигурная цитата является закрывающей, а интерпретация спецификатора формата после : нарушает правильную идентификацию закрывающей скобки.

Чтобы доказать, что строка формата экранирована как строка, учтите, что следующая

$"{date:\x6f}"

рассматривается как

$"{date:o}"

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

$"{date:MMM}}dd}}yyy}" // it a valid feb}09}2017

Анализ - это формальный процесс, основанный на правилах грамматики выражения, не может быть сделан, просто взглянув на него.

Ответ 2

Это продолжение моего первоначального ответа в порядке

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

Что касается официального источника, мы должны ссылаться на Interpolated Strings из msdn.

Структура интерполированной строки

$ " <text> { <interpolation-expression> <optional-comma-field-width> <optional-colon-format> } <text> ... } "  

и каждая отдельная интерполяция формально определяется синтаксисом

single-interpolation:  
    interpolation-start  
    interpolation-start : regular-string-literal  

interpolation-start:  
    expression  
    expression , expression  

Здесь важно, чтобы

  • optional-colon-format определяется как синтаксис regular-string-literal = > то есть он может содержать escape-sequence, в соответствии с paragraph 2.4.4.5 String literals С# Спецификация языка 5.0
  • Вы можете использовать интерполированную строку везде, где вы можете использовать string literal
  • Чтобы включить фигурные скобки ({ или }) в интерполированной строке, используйте две фигурные скобки, {{ или }} = > т.е. компилятор избегает двух фигурных скобок в optional-colon-format
  • компилятор сканирует содержащуюся интерполяцию expressions как сбалансированный текст до тех пор, пока не найдет запятую, двоеточие или не закрывает фигурные скобки = > то есть двоеточие ломает сбалансированный текст, а также закрытую фигурную скобку

Просто, чтобы быть ясным, это объясняет разницу между $"{{{date}}}", где date является expression, и поэтому она символизируется до тех пор, пока первая фигурная скобка по сравнению с $"{{{date:o}}}", где date снова будет expression и теперь он символизируется до первого двоеточия, после чего начинается регулярный строковый литерал, и компилятор возобновляет выполнение двух фигурных скобок и т.д.

Существует также Часто задаваемые вопросы о форматировании строк из msdn, где этот случай был явно обработан.

int i = 42;
string s = String.Format("{{{0:N}}}", i);   //prints ‘{N}’

Вопрос в том, почему эта последняя попытка потерпела неудачу? Есть две вещи вам нужно знать, чтобы понять этот результат:

При предоставлении спецификатора формата форматирование строк принимает эти шаги:

Определите, превышает ли спецификатор один символ: если это так, то предположим, что спецификатор является настраиваемым форматом. Пользовательский формат будет использовать подходящие замены для вашего формата, но если он не знает что делать с каким-то персонажем, он просто напишет это как литерал, найденный в формате. Определите, является ли единственный символ спецификатор является поддерживаемым спецификатором (например, "N для числа" форматирование). Если да, то форматируйте соответствующим образом. Если нет, бросьте ArgumnetException

При попытке определить, должна ли фигурная скобка экранированные фигурные скобки просто обрабатываются в том порядке, в котором они находятся получено. Следовательно, {{{ избежит первых двух символов и напечатайте литерал {, а третий фигурный скобок начнет форматирование. Исходя из этого, в }}} первые два фигурных скобки будут экранированы, поэтому литерал } будет записан в строка формата, а затем последняя фигурная скобка будет считаться закончите раздел форматирования. С этой информацией мы теперь можем выяснить, что происходит в нашей ситуации {{{0:N}}}. Первый две фигурные скобки экранированы, а затем у нас есть раздел форматирования. Однако мы также избегаем закрывающей фигурной скобки, прежде чем закрыть раздел форматирования. Поэтому наш раздел форматирования на самом деле интерпретируется как содержащий 0:N}. Теперь форматер смотрит на спецификатор формата и он видит N} для спецификатора. Поэтому интерпретирует это как пользовательский формат, и поскольку ни N, ни "означает" все для пользовательского числового формата, эти символы просто а не значение переменной, на которую ссылаются.

Ответ 3

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

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

Теперь, если мы наблюдаем "}}}" , мы можем заметить, что первая скобка охватывает интерполяцию строк, в то время как последние две должны рассматриваться как символ скобки с экранированной строкой,

Однако компилятор обрабатывает первые два как скопированный строковый символ, тем самым он вставляет строку между разделителями интерполяции. В основном компилятор делает что-то вроде этого:

string str = "a string";
$"{str'}'}"; //this would obviously generate a compile error which is bypassed by this bug

Это можно решить, переформатировав строку как таковую:

Assert.AreEqual(formatted, $"{{countdown|{$"{date:o}"}}}");

Ответ 4

Это самый простой способ заставить assert работать...

Assert.AreEqual(formatted, "{" + $"countdown|{date:o}" + "}");

В этой форме...

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

Первые две закрывающие фигурные скобки интерпретируются как буквальная закрывающая скобка, а третья - как закрытие выражения форматирования.

Это не ошибка, а ограничение грамматики для интерполированных строк. Ошибка, если таковая есть, заключается в том, что вывод форматированного текста должен быть, скорее, "o", а не просто "o".

Причина, по которой мы имеем оператор "+ =" вместо "= +" в C, С# и С++, состоит в том, что в форме = + вы не можете сказать в некоторых случаях, является ли "+" частью оператора или унарный "+".