Вступление: когда код начинает "выгибаться"
Представьте: вы пишете на C#, нужно вывести сообщение вроде Значение: {42}. Вы используете String.Format("Значение: {0}", 42) — и получаете ожидаемый результат. А теперь попробуйте вывести просто {42} без подстановки. Наивно написав:
string result = String.Format("{{42}}");
Вы ожидаете увидеть {42}, но вместо этого — ошибка формата или пустая строка. Знакомо? Этот вопрос — один из самых популярных на Stack Overflow с тысячами просмотров. И сегодня мы не просто дадим ответ, а заглянем под капот .NET, чтобы понять, как работает экранирование фигурных скобок в C#. Поехали!
Проблема: почему {{ не работает как в других языках?
В C# фигурные скобки {} — это служебные символы в строках форматирования. Они указывают, где начинается и заканчивается плейсхолдер для подстановки значения. Например, {0} означает "вставь сюда первый аргумент".
Но что, если нам нужно вывести сами фигурные скобки как текст? В большинстве языков (Python, Java) экранирование работает через удвоение: {{ даёт {. В C# — та же логика, но с подвохом: две открывающие скобки {{ превращаются в одну {, но только если они не являются частью корректного плейсхолдера.
Пример из жизни: вы пишете генератор кода на C# и нужно вывести строку if (x > 0) { return x; }. Если просто написать:
string template = "if (x > 0) { return x; }";
Компилятор или String.Format воспримет { return x; } как попытку форматирования и выбросит исключение FormatException.
Решение: правильное экранирование фигурных скобок в C#
Классический способ: String.Format и StringBuilder
Правило простое: чтобы вывести одну фигурную скобку, напишите две подряд.
// Выведет: {Hello World}
string result = String.Format("{{Hello World}}");
Console.WriteLine(result); // {Hello World}
Но есть нюанс: если вам нужно использовать и плейсхолдеры, и экранированные скобки одновременно, порядок важен. Сначала экранирование, потом подстановка:
int value = 42;
// Выведет: Значение {42} в фигурных скобках
string result = String.Format("Значение {{{0}}} в фигурных скобках", value);
Console.WriteLine(result); // Значение {42} в фигурных скобках
Разберём по косточкам: {{{0}}} — это {{ (одна скобка) + {0} (плейсхолдер) + }} (одна скобка). Итог: {42}.
Интерполяция строк ($"...") — современный подход
Начиная с C# 6, у нас есть интерполяция строк. Здесь экранирование работает иначе — через удвоение скобок, но без плейсхолдеров внутри:
int value = 42;
// Выведет: Значение {42} в фигурных скобках
string result = $"Значение {{{value}}} в фигурных скобках";
Console.WriteLine(result); // Значение {42} в фигурных скобках
Обратите внимание: в интерполяции {{ даёт {, а {value} — подстановка. Всё работает интуитивно, если помнить правило: две скобки — одна скобка.
Продвинутый случай: StringBuilder.AppendFormat
Когда работаете с StringBuilder, используйте тот же синтаксис:
var sb = new StringBuilder();
sb.AppendFormat("{{0}} = {0}", 123);
Console.WriteLine(sb.ToString()); // {0} = 123
Как это работает под капотом: разбор механизма форматирования
Давайте заглянем в исходники .NET (они открыты на GitHub!). Когда вы вызываете String.Format, внутри запускается метод FormatHelper, который итерируется по символам строки формата.
Алгоритм парсинга (упрощённо)
- Ищем символ
{. - Если следующий символ — тоже
{, то это экранированная скобка. Выводим одну{и сдвигаемся на 2 символа. - Если после
{идёт число (индекс аргумента), начинаем парсить плейсхолдер. - Если после
{идёт что-то другое — выбрасываемFormatException. - Аналогично для
}: две подряд}}дают одну}.
Вот псевдокод этого процесса:
// Упрощённая версия парсера
for (int i = 0; i < format.Length; i++)
{
if (format[i] == '{')
{
if (i + 1 < format.Length && format[i + 1] == '{')
{
result.Append('{');
i++; // пропускаем вторую скобку
}
else
{
// Парсим плейсхолдер: {0}, {0:format} и т.д.
}
}
else if (format[i] == '}')
{
if (i + 1 < format.Length && format[i + 1] == '}')
{
result.Append('}');
i++;
}
else
{
throw new FormatException("Закрывающая скобка без пары");
}
}
else
{
result.Append(format[i]);
}
}
Именно поэтому {{{0}}} работает: парсер видит {{ → выводит {, затем {0} → подстановка значения, затем }} → выводит }.
Типичные ошибки и как их избежать
Ошибка 1: Забыли экранировать одну из скобок
// Ошибка: FormatException
string bad = String.Format("Текст {0} и { скобка", 1);
Решение: всегда экранируйте обе скобки, если хотите вывести их как текст.
Ошибка 2: Путаница между интерполяцией и String.Format
// В интерполяции {{ — это скобка, а {value} — подстановка
int x = 5;
string ok = $"{{x}} = {x}"; // {x} = 5
// Но если написать {{x}} в String.Format — это будет ошибка
string err = String.Format("{{x}}"); // Выведет {x} (без подстановки)