Introduzione
Un tipo di dati stringa è uno dei tipi di dati fondamentali, insieme a quelli numerici (int, long, double) e logici (Boolean). Difficilmente puoi immaginare almeno un programma utile che non utilizzi questo tipo.
Sulla piattaforma .NET, il tipo stringa è presentato come una classe String immutabile. Inoltre, è fortemente integrato nell'ambiente CLR ed è supportato anche dal compilatore C#.
Questo articolo è dedicato alla concatenazione, un'operazione eseguita sulle stringhe con la stessa frequenza dell'operazione di addizione sui numeri. Potresti pensare:"Cosa c'è da dire?", dopo tutto, sappiamo tutti dell'operatore di stringa "+", ma come si è scoperto, ha le sue stranezze.
Specifica della lingua per l'operatore stringa “+”
La specifica del linguaggio C# fornisce tre sovraccarichi per l'operatore di stringa "+":
string operator + (string x, string y) string operator + (string x, object y) string operator + (object x, string y)
Se uno degli operandi di concatenazione di stringhe è NULL, viene inserita la stringa vuota. In caso contrario, qualsiasi argomento, che non è una stringa, viene rappresentato come una stringa chiamando il metodo virtuale ToString. Se il metodo ToString restituisce NULL, viene inserita una stringa vuota. Va notato che secondo le specifiche, questa operazione non dovrebbe mai restituire NULL.
La descrizione dell'operatore è abbastanza chiara, tuttavia, se osserviamo l'implementazione della classe String, troviamo una chiara definizione di soli due operatori “==” e “!=”. Sorge una domanda ragionevole:cosa succede dietro le quinte della concatenazione di stringhe? In che modo il compilatore gestisce l'operatore di stringa "+"?
La risposta a questa domanda non si è rivelata così difficile. Diamo un'occhiata più da vicino al metodo statico String.Concat. Il metodo String.Concat unisce una o più istanze della classe String o visualizza come valori String di una o più istanze di Object. Esistono i seguenti sovraccarichi di questo metodo:
public static String Concat (String str0, String str1) public static String Concat (String str0, String str1, String str2) public static String Concat (String str0, String str1, String str2, String str3) public static String Concat (params String[] values) public static String Concat (IEnumerable <String> values) public static String Concat (Object arg0) public static String Concat (Object arg0, Object arg1) public static String Concat (Object arg0, Object arg1, Object arg2) public static String Concat (Object arg0, Object arg1, Object arg2, Object arg3, __arglist) public static String Concat <T> (IEnumerable <T> values)
Dettagli
Supponiamo di avere la seguente espressione s =a + b, dove aeb sono stringhe. Il compilatore lo converte in una chiamata di un metodo statico Concat, ad es.
s = string.Concat (a, b)
L'operazione di concatenazione di stringhe, come qualsiasi altra operazione di addizione nel linguaggio C#, è associativa a sinistra.
Tutto è chiaro con due righe, ma cosa succede se ci sono più righe? L'espressione s =a + b + c, data l'associatività a sinistra dell'operazione, potrebbe essere sostituita da:
s = string.Concat(string.Concat (a, b), c)
Tuttavia, dato l'overloading che richiede tre argomenti, verrà convertito in:
s = string.Concat (a, b, c)
La situazione simile è con la concatenazione di quattro stringhe. Per concatenare 5 o più stringhe, abbiamo l'overloading string.Concat (params string[]), quindi è necessario tenere conto dell'overhead associato all'allocazione della memoria per un array.
Va anche notato che l'operatore di concatenazione di stringhe è completamente associativo :non importa in quale ordine concateniamo le stringhe, quindi l'espressione s =a + (b + c), nonostante la priorità di esecuzione della concatenazione esplicitamente indicata, deve essere elaborata come segue
s = (a + b) + c = string.Concat (a, b, c)
invece del previsto:
s = string.Concat (a, string.Concat (b, c))
Quindi, riassumendo quanto sopra:l'operazione di concatenazione di stringhe è sempre rappresentata da sinistra a destra e chiama il metodo statico String.Concat.
Ottimizzazione del compilatore per stringhe letterali
Il compilatore C# ha ottimizzazioni relative alle stringhe letterali. Ad esempio, l'espressione s =“a” + “b” + c, data l'associatività sinistra dell'operatore “+”, è equivalente a s =(“a” + “b”) + c è convertito in
s = string.Concat ("ab", c)
L'espressione s =c + “a” + “b”, nonostante l'associatività a sinistra dell'operazione di concatenazione (s =(c + “a”) + “b”) viene convertita in
s = string.Concat (c, "ab")
In generale, la posizione dei letterali non ha importanza, il compilatore concatena tutto ciò che può e solo allora prova a selezionare un sovraccarico appropriato del metodo Concat. L'espressione s =a + “b” + “c” + d viene convertita in
s = string.Concat (a, "bc", d)
Vanno menzionate anche le ottimizzazioni associate alle stringhe vuote e NULL. Il compilatore sa che l'aggiunta di una stringa vuota non influisce sul risultato della concatenazione, quindi l'espressione s =a + “” + b viene convertita in
s = string.Concat (a, b),
invece del previsto
s = string.Concat (a, "", b)
Allo stesso modo, con la stringa const, il cui valore è NULL, abbiamo:
const string nullStr = null; s = a + nullStr + b;
viene convertito in
s = string.Concat (a, b)
L'espressione s =a + nullStr viene convertita in s =a ?? “”, se a è una stringa e la chiamata del metodo string.Concat(a), se a non è una stringa, ad esempio s =17 + nullStr, viene convertito in s =string.Concat (17) .
Una caratteristica interessante associata all'ottimizzazione dell'elaborazione letterale e all'associatività a sinistra dell'operatore stringa "+".
Consideriamo l'espressione:
var s1 = 17 + 17 + "abc";
presa in considerazione l'associatività di sinistra, equivale a
var s1 = (17 + 17) + "abc"; // сalling the string.Concat method (34, "abc")
Di conseguenza, in fase di compilazione, vengono aggiunti i numeri, in modo che il risultato sia 34abc.
D'altra parte, l'espressione
var s2 = "abc" + 17 + 17;
è equivalente a
var s2 = ( "abc" + 17) + 17; // calling the string.Concat method ("abc", 17, 17)
il risultato sarà abc1717.
Quindi, ecco fatto, lo stesso operatore di concatenazione porta a risultati diversi.
String.Concat VS StringBuilder.Append
È necessario spendere due parole su questo confronto. Consideriamo il seguente codice:
string name = "Timur"; string surname = "Guev"; string patronymic = "Ahsarbecovich"; string fio = surname + name + patronymic;
Può essere sostituito con il codice utilizzando StringBuilder:
var sb = new StringBuilder (); sb.Append (surname); sb.Append (name); sb.Append (patronymic); string fio = sb.ToString ();
Tuttavia, in questo caso, difficilmente otterremo vantaggi dall'uso di StringBuilder. A parte il fatto che il codice è diventato meno leggibile, è diventato più o meno efficace, poiché l'implementazione del metodo Concat calcola la lunghezza della stringa risultante e alloca memoria una sola volta, contrariamente a StringBuilder che non sa nulla della lunghezza della stringa risultante.
Implementazione del metodo Concat per 3 stringhe:
public static string Concat (string str0, string str1, string str2) { if (str0 == null && str1 == null && str2 == null) return string.Empty; if (str0 == null) str0 = string.Empty; if (str1 == null) str1 = string.Empty; if (str2 == null) str2 = string.Empty; string dest = string.FastAllocateString (str0.Length + str1.Length + str2.Length); // Allocate memory for strings string.FillStringChecked (dest, 0, str0); / string.FillStringChecked (dest, str0.Length, str1); string.FillStringChecked (dest, str0.Length + str1.Length, str2); return dest; }
Operatore “+” in Java
Alcune parole sull'operatore di stringa “+” in Java. Anche se non programmo in Java, sono interessato a come funziona lì. Il compilatore Java ottimizza l'operatore "+" in modo che utilizzi la classe StringBuilder e chiami il metodo append.
Il codice precedente viene convertito in
String fio = new StringBuilder(String.valueOf(surname)).append(name).append (patronymic).ToString()
Vale la pena notare che hanno intenzionalmente rifiutato tale ottimizzazione in C#, Eric Lippert ha un post su questo argomento. Il punto è che tale ottimizzazione non è l'ottimizzazione in quanto tale, è la riscrittura del codice. Inoltre, i creatori del linguaggio C# ritengono che gli sviluppatori dovrebbero avere familiarità con gli aspetti del lavoro con la classe String e, se necessario, passare a StringBuilder.
A proposito, Eric Lippert è stato colui che ha lavorato all'ottimizzazione del compilatore C# associato alla concatenazione di stringhe.
Conclusione
Forse, a prima vista, può sembrare strano che la classe String non definisca l'operatore “+” finché non pensiamo alla capacità di ottimizzazione del compilatore relativa alla visibilità di un frammento di codice più grande. Ad esempio, se nella classe String fosse definito l'operatore “+”, l'espressione s =a + b + c + d porterebbe alla creazione di due stringhe intermedie, un'unica chiamata della stringa.Concat (a, b, c, d) il metodo consente di eseguire la concatenazione in modo più efficace.