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?

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ónUsaPor qué
Dos o tres piezas, una vez (nombre + " tiene " + edad)+ / interpolaciónLegible; una sola asignación de todos modos
Construir texto en un bucleStringBuilderO(n) en vez de O(n²)
Unir una colecciónstring.JoinHerramienta hecha para eso, en una llamada
Valor de texto inmutablestringEs 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?