Фигурные скобки в C#: как экранировать и что внутри

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Вступление: когда код начинает "выгибаться"

Представьте: вы пишете на 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, который итерируется по символам строки формата.

Алгоритм парсинга (упрощённо)

  1. Ищем символ {.
  2. Если следующий символ — тоже {, то это экранированная скобка. Выводим одну { и сдвигаемся на 2 символа.
  3. Если после { идёт число (индекс аргумента), начинаем парсить плейсхолдер.
  4. Если после { идёт что-то другое — выбрасываем FormatException.
  5. Аналогично для }: две подряд }} дают одну }.

Вот псевдокод этого процесса:

// Упрощённая версия парсера
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} (без подстановки)

Ошибка

Блоги

Рекомендаций по книгам