Tipos · strings
string frente a StringBuilder en C#
El titular que lo explica todo: los strings de C# son inmutables — ningún método de string modifica un string; todos devuelven uno nuevo. StringBuilder existe por ese hecho.
Inmutabilidad, demostrada
Ejecuta esto y mira cómo una llamada a método no hace nada:
string s = "hola";
s.Replace("o", "0"); // devuelve un string NUEVO — s queda intacto
Console.WriteLine(s);
string s = "hola";
s = s.Replace("o", "0"); // recoge el string nuevo
Console.WriteLine(s); // h0la
s = s.ToUpper(); // la misma regla para cada método de string
Console.WriteLine(s); // HOLA
// el literal original nunca cambió — se fabricaron strings nuevos:
Console.WriteLine("original intacto: " + ("hola" == "hola"));
Cuando esto hace clic, una familia entera de bugs desaparece: "¿por qué mi Replace no hace nada?" — porque tiraste el valor de retorno. Los strings son valores que resultan ser texto; trata el resultado de cada método como la salida real.
¿Por qué inmutables?
- Compartir seguro. Pasar un string a un método nunca permite que el método cambie "tu copia" — no hay nada que cambiar. (Las clases son lo contrario: alias de referencia.)
- Interning gratis. Los literales idénticos pueden compartir almacenamiento con seguridad — por eso
"manzana" == "manzana"puede ser una comparación de referencia que igual da True. - Hasheables. Una clave inmutable no puede cambiar su hash — por eso los strings sirven como claves de
Dictionary.
El coste: construir strings en un bucle
string msg = "";
for (int i = 1; i <= 5; i++)
{
msg += i + ","; // cada += fabrica un string nuevo: el viejo + la parte nueva
}
Console.WriteLine(msg);
Cinco iteraciones, da igual. Ahora las cuentas: la iteración n copia todos los caracteres acumulados (n−1) para fabricar el string nuevo. El copiado total crece como 1+2+3+…+n ≈ n²/2 caracteres. Con n = 100.000 son miles de millones de copias y una montaña de basura para el recolector. Ese es el bug que StringBuilder existe para arreglar.
StringBuilder: un búfer mutable
StringBuilder mantiene un búfer interno y hace append dentro de él — sin string nuevo por append y sin copiar el contenido anterior. Solo pagas una vez, al final, cuando pides el string terminado:
// StringBuilder no está en el subconjunto del motor del navegador — corre tal cual en Visual Studio
using System.Text;
var sb = new StringBuilder();
for (int i = 1; i <= 100000; i++)
{
sb.Append(i);
sb.Append(',');
}
string msg = sb.ToString(); // un solo string al final
Prueba la versión con bucle para n = 100.000 en el motor y siente la diferencia; luego confía en que StringBuilder hace el mismo trabajo en un parpadeo. (El equivalente dentro del motor es List<T> + string.Join: var partes = new List<string>(); … string.Join(",", partes).)
Entonces, ¿cuál usar?
| Situación | Usa | Por qué |
|---|---|---|
Dos o tres piezas, una vez (nombre + " tiene " + edad) | + / interpolación | Legible; una sola asignación de todos modos |
| Construir texto en un bucle | StringBuilder | O(n) en vez de O(n²) |
| Unir una colección | string.Join | Herramienta hecha para eso, en una llamada |
| Valor de texto inmutable | string | Es el valor por defecto — y la red de seguridad |
Regla práctica: pocas piezas → interpola; bucle → StringBuilder; colección → Join. No optimices antes de tiempo el caso de dos piezas — $"{nombre} tiene {edad}" es lo más rápido de escribir y suficientemente rápido de ejecutar.
Autotest de un minuto
Ambas líneas imprimen — predice cada una antes de ejecutar en el playground:
string s = "gato";
s.Replace("g", "p");
Console.WriteLine(s); // ¿"pato" o "gato"? (inmutabilidad)
s = s.Replace("g", "p");
Console.WriteLine(s); // ¿y ahora?