Il contenuto di questo articolo Γ¨ una libera traduzione dellβarticolo di Jon Skeet trovabile qui.
1.Introduzione
Il tipo System.String
(abbreviato in string
) Γ¨ uno dei tipi piΓΉ importanti del .NET e, purtroppo, non viene usato spesso correttamente.
In questo articolo troveremo tutte le caratteristiche base del tipo, oltre ad un approfondimento su come concatenare stringhe usando lo StringBuilder
.
1.1 Cosa Γ¨ una stringa?
Una stringa Γ¨ una sequenza di caratteri. Ogni carattere Γ¨ un Unicode
nel range che parte da U+0000
a U+FFFF
. Il tipo string ha le seguenti caratteristiche:
- Γ¨ un reference type: data la sua immutabilitΓ (vedi il punto successivo), spesso viene confuso con un value type in quanto si comporta in maniera analoga (vedi approfondimento qui).
- Γ¨ immutabile: il contenuto di una stringa nonn puΓ² mai essere cambiato. A causa di questo spesso non viene cambiato il contenuto, ma la variabile. Per esempio, il
codice s = s.Replace ("foo", "bar");
non cambia il contenuto della stringas
di origine, ma cambia il valore dis
ad una nuova stringa, che Γ¨ una copia di quella vecchia con βfooβ sostituito da βbarβ - PuΓ² contenere valori a null: un programmatore di C Γ¨ abituato al fatto che le stringhe sono sequenze di caratteri terminati da
\0
, il caratterenull
. In C#, a differenza del C, le stirnghe possono contenere piΓΉ caratteri null, perΓ² altre classi spesso considerano una stringa terminata al primo valorenull
, quindi allβatto pratico Γ¨ come il C. - Sovrascrive lβoperatore: quando viene usato lβoperatore === per confrontare due stringhe, viene chiamato il metodo
Equals
, che controlla lβuguaglianza del contenuto delle due stringhe invece della loro referenza (cosa che invece avviene con lβutilizzo di === con altri tipi di dato). Conseguentemente"hello".Substring(0, 4)=="hell"
Γ¨ uguale a true anche se i puntatori delle due stringhe sono ovviamente diversi.
1.2 Letterali (literals)
I literlas sono il modo in cui una stringa viene hard codata nel codice. Vi sono due tipi di letterali in C#
- Letterali regolari: cominciano e terminano con
"
ed alcuni caratteri (come"
,\
, CF e LF) devono essere escapati al suo interno - Letterali verbatim: permettono qualsiasi letterale allβinterno della stringa e si distinguono per avere una chiocciola @ prima della virgoletta iniziale
Letterali regolari | Letterali verbatim | Risultato |
---|---|---|
βHelloβ | @βHelloβ | Hello |
βBackslash: \β | @βBackslash: β | Backslash: | |
βQuote: "" | @βQuote: """ | Quote: " |
"CRLF:\r\nPost CRLFβ | @βCRLF: Post CRLFβ | CRLF: Post CRLF |
2. Concatenazione di stringhe in .NET
Il contenuto di questo articolo Γ¨ una libera traduzione dellβarticolo di Jon Skeet trovabile qui.
Uno dei primi consigli di efficenza che un programmatore .NET riceve Γ¨ usare StringBuilder
per concatenare le stringhe a causa del problema descritto nella sezione successiva.
2.1 Problema
Il problema di presenta quando devo costruire una stringa con grandezza crescente, come nellβesempio seguente
string x = "";
for (int i=0; i < 100000; i++)
{
x += "!";
}
Unβoperazione del genere impiega circa 10 secondi, raddoppiando il numero di iterazioni anche un minuto. Questo perchΓ¨ le stringhe, come indicato nella sezione introduttiva, sono immutabili: il fatto di usare +=
non significa che ho un append dinamico del carattere β!β alla stringa, infatti x += "!"
Γ¨ identico a x = x+"!"
, che significa creare una stringa completamente nuova, allocare sufficente memoria per tutto, copiare tutti i valori di x
alla nuova variabile aggiungendo un β!β alla fine.
Mano a mano che la stringa cresce, crescono anche i dati da copiare da una variabile allβaltra.
Questo metodo è ovviamente inefficente, e quì viene in aiuto lo StringBuilder
.
2.2 Soluzione
Lβesempio indicato sotto Γ¨ analogo nel comportamento a quello della sezione precedente ma con lβutilizzo dello StringBuilder
.
StringBuilder builder = new StringBuilder();
for (int i=0; i < 100000; i++)
{
builder.Append("!");
}
string x = builder.ToString();
Questo metodo impiega 40ms con milioni di iterazioni, un incremento di performance notevole.
Questo accade in quanto non ho la copia continua di dati: solo la stringa che stiamo appendendo viene copiata in quanto StringBuilder ha un suo buffer interno e i nuovi caratteri vengono aggiunti a questo buffer. Questo viene ingrandito (solitamente raddoppiato) solo se non puΓ² contenere piΓΉ dati.
Qualora conoscessimo la lunghezza finale della stringa (ed Γ¨ questo il caso) possiamo rendere il metodo sopra ancora piΓΉ efficente inserendola a costruttore dello StringBuilder
: in questo caso non ho la copiatura di alcun dato.
2.3 Quando usare lo StringBuilder
Non sempre Γ¨ vero che per concatenare le stringhe la soluzione migliore sia lo StringBuilder
, dipende caso per caso.
Per esempio, consideriamo il seguente codice, che utilizza la normale concatenazione:
string name = firstName + " " + lastName;
Person person = new Person (name);
confrontato con il seguente che invece usa lo StringBuilder
:
// Bad code! Do not use!
StringBuilder builder = new StringBuilder();
builder.Append (firstName);
builder.Append (" ");
builder.Append (lastName);
string name = builder.ToString();
Person person = new Person (name);
Avere un codice incredibilmente meno leggibile per ottenere un guadagno di prestazioni così marginale è sicuramente sbagliato, ammettendo che la seconda versione sia comunque più efficente. Il problema è che così non è!
La prima versione (assumendo che firstName e lastName siano vere variabili e non costanti) compila con String.Concat
nel modo seguente:
string name = String.Concat (firstName, " ", lastName);
Person person = new Person (name);
String.Concat
prende un insieme di stringhe in ingresso e le concatena. Ora, String.Concat
puΓ² sapere la lunghezza delle stringhe da concatenare prima che questa avvenga, conseguentemente non Γ¨ encessaria nessuna copiatura inutile in quanto i dati sono copiati nella nuova stirnga che Γ¨ esattamente della lunghezza giusta.
StringBuilder
invece non conosce la grandezza del buffer da utilizzare e probabilmente, in questo caso, utilizzerΓ un buffer di grandezza maggiore di quello necessario, oltre allβoverhead dovuto dallβutilizzo di un oggetto aggiuntivo (lo StringBuilder
stesso).
La differenza fondamentale tra i due esempi Γ¨ che in questo caso ho a disposizione tutte le stringhe da passare allo String.Concat
, senza passare per stringhe intermedie. StringBuilder
invece Γ¨ utile come contenitore per gestire numerose stringhe intermedie ed eviatare conseguentemente il processo di copia.
2.4 Costanti
Le cose diventano piΓΉ intricate quando si lavora con stringhe costanti (letterali, e const string
).
Per esempio, cosa succede con questa operazione?
string x = "hello" + " " + "there";
Ci si aspetta che ci sia una chiamata a String.Concat
, ma così non è. Questo codice viene compilato esattamente allo stesso modo del codice
string x = "hello there";
in quanto il compilatore conosce che tutte le parti della stirnga sono costanti, conseguentemente esegue tutte le concatenazioni durante la compilazione del codice, memorizzando la stirnga completa nel codice compilato.
Convertire il codice sopra con uno StringBuilder
Γ¨ inefficente di memoria e velocitΓ , oltre a ridurre la leggibilitΓ del codice.
2.5 Conclusioni
Ricapitoliamo quando usare lo StringBuilder
e quando invece la concatenazione normale:
- Usare sempre lo
StringBuilder
quando sto effettuando delle concatenazioni allβinterno di cicli (non banali), specialmente quando non conosco quante operazioni effettuerΓ il ciclo. Per esempio, leggere un file un carattere alla volta e creare una stringa con il+=
Γ¨ un suicidio. - Usare la concatenazione quando posso specificare tutto quello che deve essere concatenato in una sola riga di codice (se ho un array considerare
String.Concat
oString.Join
) - Non preoccuparsi di rompere i letterali in piΓΉ pezzi concatenati, al fine di una maggiore leggibilitΓ . Il risultato, a livello di performance, sarΓ lo stesso