Introducción: cuando el código empieza a "torcerse"
\n\nImagine: está escribiendo en C#, necesita mostrar un mensaje como Valor: {42}. Usa String.Format("Valor: {0}", 42) — y obtiene el resultado esperado. Ahora intente mostrar simplemente {42} sin sustitución. Escribiendo ingenuamente:
string result = String.Format("{{42}}");\n\nEspera ver {42}, pero en su lugar obtiene un error de formato o una cadena vacía. ¿Le suena? Esta pregunta es una de las más populares en Stack Overflow con miles de visitas. Y hoy no solo daremos la respuesta, sino que miraremos bajo el capó de .NET para entender cómo funciona el escapado de llaves en C#. ¡Allá vamos!
El problema: ¿por qué {{ no funciona como en otros lenguajes?
\n\nEn C#, las llaves {} son caracteres especiales en las cadenas de formato. Indican dónde comienza y termina un marcador de posición para la sustitución de un valor. Por ejemplo, {0} significa "inserta aquí el primer argumento".
Pero, ¿qué pasa si necesitamos mostrar las propias llaves como texto? En la mayoría de los lenguajes (Python, Java), el escapado funciona duplicando: {{ da {. En C#, la lógica es la misma, pero con un truco: dos llaves de apertura {{ se convierten en una {, pero solo si no forman parte de un marcador de posición válido.
Ejemplo de la vida real: está escribiendo un generador de código en C# y necesita mostrar la cadena if (x > 0) { return x; }. Si simplemente escribe:
string template = "if (x > 0) { return x; }";\n\nEl compilador o String.Format interpretará { return x; } como un intento de formateo y lanzará una excepción FormatException.
Solución: escapado correcto de llaves en C#
\n\nMétodo clásico: String.Format y StringBuilder
\n\nLa regla es simple: para mostrar una llave, escriba dos seguidas.
\n\n// Mostrará: {Hello World}\nstring result = String.Format("{{Hello World}}");\nConsole.WriteLine(result); // {Hello World}\n\nPero hay un matiz: si necesita usar tanto marcadores de posición como llaves escapadas al mismo tiempo, el orden es importante. Primero el escapado, luego la sustitución:
\n\nint value = 42;\n// Mostrará: Valor {42} entre llaves\nstring result = String.Format("Valor {{{0}}} entre llaves", value);\nConsole.WriteLine(result); // Valor {42} entre llaves\n\nAnalicemos en detalle: {{{0}}} es {{ (una llave) + {0} (marcador de posición) + }} (una llave). Resultado: {42}.
Interpolación de cadenas ($"...") — enfoque moderno
\n\nA partir de C# 6, tenemos interpolación de cadenas. Aquí el escapado funciona de manera diferente: duplicando las llaves, pero sin marcadores de posición internos:
\n\nint value = 42;\n// Mostrará: Valor {42} entre llaves\nstring result = $"Valor {{{value}}} entre llaves";\nConsole.WriteLine(result); // Valor {42} entre llaves\n\nObserve: en la interpolación, {{ da {, y {value} es la sustitución. Todo funciona de forma intuitiva si recuerda la regla: dos llaves son una llave.
Caso avanzado: StringBuilder.AppendFormat
\n\nCuando trabaje con StringBuilder, use la misma sintaxis:
var sb = new StringBuilder();\nsb.AppendFormat("{{0}} = {0}", 123);\nConsole.WriteLine(sb.ToString()); // {0} = 123\n\nCómo funciona internamente: análisis del mecanismo de formato
\n\nMiremos dentro del código fuente de .NET (¡está abierto en GitHub!). Cuando llama a String.Format, internamente se ejecuta el método FormatHelper, que itera sobre los caracteres de la cadena de formato.