Menu

StringBuilder in C#: Append, Insert, Replace e quando usarlo

Perché concatenare stringhe ripetutamente in un ciclo è lento in C#, e come lo risolve StringBuilder: Append, AppendLine, AppendFormat, Insert, Remove, Replace, capacità, e quando la semplice concatenazione o string.Join sono la scelta migliore.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

In C# le stringhe sono immutabili: s += "x" non allunga s, ma crea una nuova stringa che contiene i vecchi caratteri più quelli nuovi, e la vecchia stringa diventa spazzatura. Una volta non è nulla. In un ciclo che gira migliaia di volte, ogni iterazione copia tutto quello costruito fino a quel momento, quindi il lavoro totale cresce con il quadrato della lunghezza.

StringBuilder, in System.Text, è un buffer modificabile. Append scrive nello spazio libero in fondo, ingrandendo il buffer ogni tanto, e ToString() crea una sola stringa alla fine.

Output:

Order #1042, total 59.97
Status: shipped
**********
51

Perché concatenare in un ciclo è lento

Ecco lo stesso testo costruito in entrambi i modi. I tempi cambiano da una macchina all'altra, quindi i numeri qui sotto sono solo un esempio; conta il divario, che si allarga man mano che il ciclo cresce:

Output di esempio:

string +=     482 ms, length 188890
StringBuilder 1 ms, length 188890

Con 20.000 aggiunte, la versione con += copia in totale quasi 2 miliardi di caratteri. StringBuilder copia ogni carattere circa due volte, una quando viene aggiunto e un'altra in ToString(), ed è per questo che resta intorno al millisecondo.

Append, AppendLine e AppendFormat

Append ha overload per ogni tipo predefinito e restituisce lo stesso builder, quindi le chiamate si possono concatenare. AppendLine aggiunge un a capo dopo il suo argomento. AppendFormat accetta una stringa di formato composito come string.Format:

Output:

RECEIPT
-------
Espresso        2.40
Croissant       3.10
Orange juice    4.50
TOTAL          10.00

Aggiungere una stringa interpolata (sb.Append($"...")) va bene e si legge facilmente. Su .NET 6 e successivi il compilatore scrive addirittura i pezzi direttamente nel builder, senza creare la stringa intermedia.

AppendLine usa Environment.NewLine: \n su Linux e macOS, \r\n su Windows. Quando un formato di file richiede un fine riga particolare, aggiungilo tu.

Insert, Remove, Replace e l'indicizzatore

A differenza di una stringa, uno StringBuilder si può modificare sul posto:

Output:

Hello, World
Hello, Maya
Maya
maya
maya!
[] length 0

Impostare Length a un valore più piccolo tronca il contenuto; è il modo più economico per togliere un separatore finale. Clear() porta la lunghezza a 0 e mantiene il buffer allocato, quindi un solo builder può essere riusato tra le iterazioni di un ciclo esterno.

L'indicizzatore è veloce per le aggiunte recenti, ma può essere lento per l'accesso casuale in un builder molto grande, perché il contenuto è memorizzato in blocchi collegati. Se devi leggere dei caratteri, chiama ToString() una volta e indicizza la stringa.

Il problema del separatore finale

Un uso classico è costruire un elenco separato da virgole. Aggiungere un separatore dopo ogni elemento ne lascia uno di troppo alla fine:

Output:

csharp, dotnet, linq
csharp, dotnet, linq

Quando gli elementi sono già in una collezione, string.Join è più breve e almeno altrettanto veloce. Tieni StringBuilder per il testo la cui forma dipende dalla logica: sezioni condizionali, cicli annidati, formattazione mista.

Capacità

Uno StringBuilder parte con spazio per 16 caratteri. Quando lo esaurisce, collega un nuovo blocco di memoria grande quanto tutto quello che contiene già, fino a 8.000 caratteri per blocco, quindi la sua capacità raddoppia finché è piccolo e poi cresce a passi di 8.000 caratteri. Crescere non copia mai il testo già memorizzato. Se sai più o meno quanto sarà lungo il risultato, passa la capacità al costruttore e il builder non dovrà mai crescere:

Output:

12000
10000
True
True

Preallocare è un'ottimizzazione, non un obbligo. La crescita alloca un nuovo blocco ma non sposta mai il testo esistente, quindi il valore predefinito va bene, a meno che tu non stia costruendo molte stringhe grandi in un percorso critico.

Quando non usare StringBuilder

StringBuilder non è un sostituto più veloce per ogni +. Il compilatore trasforma già a + b + c + d in una singola espressione in un'unica chiamata a string.Concat, che calcola la lunghezza finale e copia ogni pezzo una volta sola: lo stesso lavoro che farebbe un builder, con più codice. Usa le stringhe normali quando:

  • Il numero di pezzi è piccolo e fisso: $"{first} {last} ({age})".
  • I pezzi sono in una collezione e serve un separatore: string.Join(", ", items).
  • I pezzi non hanno bisogno di separatore: string.Concat(parts).
  • Concateni poche volte, fuori da qualsiasi ciclo.

Usa StringBuilder quando aggiungi testo dentro un ciclo, lo aggiungi un numero di volte che non conosci in anticipo, oppure modifichi il testo (inserisci, sostituisci, rimuovi) mentre lo costruisci. Convertire avanti e indietro (sb.ToString() dentro il ciclo) butta via il vantaggio; chiamalo una sola volta alla fine.

L'altra alternativa sono gli stream: per produrre un file grande, scrivi le righe su uno StreamWriter man mano, invece di costruire tutto il contenuto in memoria. Vedi file.

Domande frequenti

Cos'è StringBuilder in C#?

System.Text.StringBuilder è un buffer modificabile di caratteri per costruire una stringa in molti passaggi. Append aggiunge in fondo al buffer invece di creare ogni volta una nuova stringa, e ToString() produce la stringa finale una sola volta. È lo strumento standard per costruire testo in un ciclo.

Quando conviene usare StringBuilder invece della concatenazione di stringhe?

Quando aggiungi testo in un ciclo o un numero imprecisato di volte, per esempio per costruire un report, un file CSV o dell'HTML a partire da una collezione. Per pochi pezzi fissi (a + b + c, o una sola stringa interpolata) la semplice concatenazione è altrettanto veloce e più leggibile, perché il compilatore la combina già in un'unica chiamata a string.Concat.

Come rimuovo l'ultimo carattere da uno StringBuilder?

Riduci la sua Length: if (sb.Length > 0) sb.Length--; elimina l'ultimo carattere senza copiare. sb.Remove(sb.Length - 1, 1) fa lo stesso. È il modo abituale per togliere una virgola finale, anche se string.Join evita del tutto il separatore finale.

StringBuilder è thread-safe?

No. Chiamare Append sullo stesso StringBuilder da più thread contemporaneamente può corromperne il contenuto o lanciare un'eccezione. Dai a ogni thread il suo builder e combina i risultati, oppure proteggi quello condiviso con un lock.

Cosa aggiunge AppendLine in C#?

Il testo seguito da Environment.NewLine, che è \r\n su Windows e \n su Linux e macOS. Se l'output deve usare un fine riga specifico, come \n per un formato di file o \r\n per un header HTTP, aggiungilo esplicitamente con Append("\n").

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA