Quando è meglio usare String.Format rispetto alla concatenazione di stringhe?


120

Ho un piccolo pezzo di codice che sta analizzando un valore di indice per determinare un input di cella in Excel. Mi ha fatto pensare ...

Qual è la differenza tra

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

e

xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);

Uno è "migliore" dell'altro? E perché?



possibile duplicato di Why use String.Format?
Druido

Risposte:


115

Prima di C # 6

Ad essere onesti, penso che la prima versione sia più semplice, anche se la semplificherei per:

xlsSheet.Write("C" + rowIndex, null, title);

Sospetto che altre risposte possano parlare del calo delle prestazioni, ma ad essere onesti sarà minimo se presente - e questa versione di concatenazione non ha bisogno di analizzare la stringa di formato.

Le stringhe di formato sono ottime per scopi di localizzazione ecc., Ma in un caso come questo la concatenazione è più semplice e funziona altrettanto bene.

Con C # 6

L'interpolazione di stringhe rende molte cose più semplici da leggere in C # 6. In questo caso, il secondo codice diventa:

xlsSheet.Write($"C{rowIndex}", null, title);

che è probabilmente l'opzione migliore, IMO.



Lo so, lo so. È stato fatto per scherzo (ho letto il link prima, che è stata una buona lettura)
nawfal

Jon. Sono sempre stato un fan del signor Richter e ho seguito religiosamente le indicazioni sulla boxe, ecc. Tuttavia, dopo aver letto il tuo (vecchio) articolo, ora sono un convertito. Grazie
stevethread


4
Ora che C # 6 è disponibile, puoi usare la nuova sintassi di interpolazione di stringhe per quella che penso sia ancora più leggibile:xlsSheet.Write($"C{rowIndex}", null, title);
HotN

158

La mia preferenza iniziale (proveniente da uno sfondo C ++) era per String.Format. L'ho abbandonato in seguito per i seguenti motivi:

  • La concatenazione di stringhe è probabilmente "più sicura". A me è capitato (e l'ho visto accadere a molti altri sviluppatori) di rimuovere un parametro o di rovinare l'ordine dei parametri per errore. Il compilatore non controllerà i parametri rispetto alla stringa di formato e si finisce con un errore di runtime (cioè, se sei abbastanza fortunato da non averlo in un metodo oscuro, come la registrazione di un errore). Con la concatenazione, la rimozione di un parametro è meno soggetta a errori. Si potrebbe sostenere che la possibilità di errore è molto piccola, ma può accadere.

- La concatenazione di stringhe consente valori nulli, String.Formatno. La scrittura di " s1 + null + s2" non si interrompe, tratta semplicemente il valore null come String.Empty. Bene, questo può dipendere dal tuo scenario specifico: ci sono casi in cui desideri un errore invece di ignorare silenziosamente un FirstName nullo. Tuttavia, anche in questa situazione, personalmente preferisco controllare personalmente i valori nulli e lanciare errori specifici invece dell'eccezione ArgumentNullException standard che ottengo da String.Format.

  • La concatenazione di stringhe funziona meglio. Alcuni dei post sopra menzionano già questo (senza spiegarne effettivamente il motivo, il che mi ha determinato a scrivere questo post :).

L'idea è che il compilatore .NET sia abbastanza intelligente da convertire questo pezzo di codice:

public static string Test(string s1, int i2, int i3, int i4, 
        string s5, string s6, float f7, float f8)
{
    return s1 + " " + i2 + i3 + i4 + " ddd " + s5 + s6 + f7 + f8;
}

a questa:

public static string Test(string s1, int i2, int i3, int i4,
            string s5, string s6, float f7, float f8)
{
    return string.Concat(new object[] { s1, " ", i2, i3, i4, 
                    " ddd ", s5, s6, f7, f8 });
}

Cosa succede sotto il cofano di String.Concat è facile da indovinare (usa Reflector). Gli oggetti nell'array vengono convertiti nella loro stringa tramite ToString (). Quindi viene calcolata la lunghezza totale e viene allocata solo una stringa (con la lunghezza totale). Infine, ogni stringa viene copiata nella stringa risultante tramite wstrcpy in una parte di codice non sicura.

I motivi String.Concatsono molto più veloci? Bene, possiamo tutti dare un'occhiata a cosa String.Formatsta facendo: rimarrai sorpreso dalla quantità di codice richiesta per elaborare la stringa di formato. Inoltre (ho visto commenti sul consumo di memoria), String.Formatutilizza internamente uno StringBuilder. Ecco come:

StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));

Quindi per ogni argomento passato, riserva 8 caratteri. Se l'argomento è un valore di una cifra, allora peccato, abbiamo un po 'di spazio sprecato. Se l'argomento è un oggetto personalizzato che restituisce del testo lungoToString() , potrebbe essere necessaria anche una riallocazione (scenario peggiore, ovviamente).

Rispetto a questo, la concatenazione spreca solo lo spazio dell'array di oggetti (non troppo, tenendo conto che è un array di riferimenti). Non esiste alcuna analisi per gli identificatori di formato e nessuno StringBuilder intermediario. L'overhead di boxe / unboxing è presente in entrambi i metodi.

L'unico motivo per cui scegliere String.Format è quando è coinvolta la localizzazione. L'inserimento di stringhe di formato nelle risorse ti consente di supportare lingue diverse senza interferire con il codice (pensa agli scenari in cui i valori formattati cambiano ordine a seconda della lingua, ad esempio "dopo {0} ore e {1} minuti" potrebbe avere un aspetto molto diverso in giapponese: ).


Per riassumere il mio primo (e piuttosto lungo) post:

  • Il modo migliore (in termini di prestazioni vs manutenibilità / leggibilità) per me è usare la concatenazione di stringhe, senza alcuna ToString()chiamata
  • se stai cercando la performance, ToString()chiama tu stesso per evitare la boxe (sono un po 'sbilanciato verso la leggibilità) - come la prima opzione nella tua domanda
  • se mostri stringhe localizzate all'utente (non è il caso qui), String.Format()ha un vantaggio.

5
1) string.Formatè "sicuro" quando si utilizza ReSharper; cioè, è sicuro come qualsiasi altro codice che può essere utilizzato [in modo errato]. 2) string.Format non consentire una "sicura" null: string.Format("A{0}B", (string)null)risultati in "AB". 3) Raramente mi interessa questo livello di prestazioni (ea tal fine, è un raro giorno in cui mi ritiro StringBuilder) ...

D'accordo su 2), modificherò il post. Non è possibile verificare se questo fosse sicuro nella versione 1.1, ma il framework più recente è effettivamente null-safe.
Dan C.

String.Concat è ancora utilizzato se uno degli operandi è una chiamata al metodo con un valore di ritorno, anziché essere un parametro o una variabile?
Richard Collette

2
@RichardCollette Sì, String.Concat viene utilizzato anche se concatenate i valori di ritorno delle chiamate di metodo, ad esempio string s = "This " + MyMethod(arg) + " is a test";viene compilato in una String.Concat()chiamata in modalità Release.
Dan C.

Risposta fantastica; scritto e spiegato molto bene.
Frank V

6

Penso che la prima opzione sia più leggibile e questa dovrebbe essere la tua preoccupazione principale.

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

string.Format utilizza uno StringBuilder sotto il cofano (controlla con il riflettore ) quindi non avrà alcun vantaggio in termini di prestazioni a meno che tu non stia facendo una quantità significativa di concatenazione. Sarà più lento per il tuo scenario, ma la realtà è che questa decisione di ottimizzazione delle micro prestazioni è inappropriata per la maggior parte del tempo e dovresti davvero concentrarti sulla leggibilità del tuo codice a meno che non ti trovi in ​​un ciclo.

In ogni caso, scrivi prima per leggibilità e poi usa un profiler delle prestazioni per identificare i tuoi hotspot se pensi davvero di avere problemi di prestazioni.



5

Per un semplice caso in cui si tratta di una semplice concatenazione singola, sento che non vale la complessità di string.Format(e non l'ho testato, ma sospetto che per un caso semplice come questo, string.Format potrebbe essere leggermente più lento, con l'analisi della stringa di formato e tutto). Come Jon Skeet, preferisco non chiamare esplicitamente .ToString(), poiché verrà fatto implicitamente dastring.Concat(string, object) sovraccarico e penso che il codice sia più pulito e più facile da leggere senza di esso.

Ma per più di poche concatenazioni (quante sono soggettive), preferisco decisamente string.Format. Ad un certo punto penso che sia la leggibilità che le prestazioni soffrano inutilmente della concatenazione.

Se ci sono molti parametri nella stringa di formato (di nuovo, "molti" è soggettivo), di solito preferisco includere indici commentati sugli argomenti di sostituzione, per non perdere traccia di quale valore va a quale parametro. Un esempio artificioso:

Console.WriteLine(
    "Dear {0} {1},\n\n" +

    "Our records indicate that your {2}, \"{3}\", is due for {4} {5} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary",

    /*0*/client.Title,
    /*1*/client.LastName,
    /*2*/client.Pet.Animal,
    /*3*/client.Pet.Name,
    /*4*/client.Pet.Gender == Gender.Male ? "his" : "her",
    /*5*/client.Pet.Schedule[0]
);

Aggiornare

Mi viene in mente che l'esempio che ho fornito è un po 'confuso, perché sembra che abbia usato sia la concatenazione che string.Formatqui. E sì, logicamente e lessicalmente, è quello che ho fatto. Ma le concatenazioni saranno tutte ottimizzate dal compilatore 1 , poiché sono tutte stringhe letterali. Quindi in fase di esecuzione, ci sarà una singola stringa. Quindi credo di dover dire che preferisco evitare molte concatenazioni in fase di esecuzione .

Ovviamente, la maggior parte di questo argomento è obsoleta ora, a meno che non si sia ancora bloccati a utilizzare C # 5 o versioni precedenti. Ora abbiamo stringhe interpolate , che per leggibilità sono di gran lunga superiori string.Format, in quasi tutti i casi. In questi giorni, a meno che non sto solo concatenando un valore direttamente all'inizio o alla fine di una stringa letterale, utilizzo quasi sempre l'interpolazione di stringhe. Oggi scriverei il mio esempio precedente in questo modo:

Console.WriteLine(
    $"Dear {client.Title} {client.LastName},\n\n" +

    $"Our records indicate that your {client.Pet.Animal}, \"{client.Pet.Name}\", " +
    $"is due for {(client.Pet.Gender == Gender.Male ? "his" : "her")} " +
    $"{client.Pet.Schedule[0]} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary"
);

In questo modo si perde la concatenazione in fase di compilazione. Ogni stringa interpolata viene trasformata in una chiamata a string.Formatdal compilatore ei risultati vengono concatenati in fase di esecuzione. Ciò significa che questo è un sacrificio delle prestazioni in fase di esecuzione per la leggibilità. Il più delle volte, è un sacrificio utile, perché la penalità del tempo di esecuzione è trascurabile. Nel codice critico per le prestazioni, tuttavia, potrebbe essere necessario profilare soluzioni diverse.


1 Puoi vederlo nella specifica C # :

... i seguenti costrutti sono consentiti nelle espressioni costanti:

...

  • L'operatore binario predefinito + ...

Puoi anche verificarlo con un piccolo codice:

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";

1
Cordiali saluti, puoi usare @ "... multi line string" invece di tutte le concatenazioni.
Aaron Palmer

Sì, ma poi devi giustificare a sinistra la tua stringa. Le stringhe @ includono tutte le nuove righe e i caratteri di tabulazione tra virgolette.
P Daddy

So che è vecchio, ma questo è un caso in cui direi di mettere la stringa di formato in un file resx.
Andy

2
Wow, tutti si sono concentrati sulla stringa letterale, invece che sul nocciolo della questione.
P Daddy

heheh - Stavo solo notando la concatenazione di stringhe all'interno del tuoString.Format()
Kristopher

3

Se la tua stringa fosse più complessa con molte variabili concatenate, allora sceglierei string.Format (). Ma per la dimensione della stringa e il numero di variabili concatenate nel tuo caso, sceglierei la tua prima versione, è più spartana .


3

Ho dato un'occhiata a String.Format (usando Reflector) e in realtà crea uno StringBuilder quindi chiama AppendFormat su di esso. Quindi è più veloce del concatenamento per più agitazioni. Il più veloce (credo) sarebbe creare uno StringBuilder e fare le chiamate ad Append manualmente. Ovviamente il numero di "molti" è da indovinare. Vorrei usare + (in realtà e perché sono principalmente un programmatore VB) per qualcosa di semplice come il tuo esempio. Man mano che diventa più complesso, uso String.Format. Se ci sono MOLTE variabili, sceglierei StringBuilder e Append, ad esempio, abbiamo codice che crea codice, lì uso una riga di codice effettivo per produrre una riga di codice generato.

Sembra che ci siano alcune speculazioni su quante stringhe vengono create per ciascuna di queste operazioni, quindi prendiamo alcuni semplici esempi.

"C" + rowIndex.ToString();

"C" è già una stringa.
rowIndex.ToString () crea un'altra stringa. (@manohard - non si verificherà alcun boxing di rowIndex)
Quindi otteniamo la stringa finale.
Se prendiamo l'esempio di

String.Format("C(0)",rowIndex);

quindi abbiamo "C {0}" come stringa
rowIndex viene inscatolato per essere passato alla funzione
Viene creato un nuovo generatore di stringhe
AppendFormat viene chiamato nel generatore di stringhe - Non conosco i dettagli di come funzioni AppendFormat ma supponiamo che lo sia ultra efficiente, dovrà comunque convertire il rowIndex boxed in una stringa.
Quindi converti lo stringbuilder in una nuova stringa.
So che StringBuilders tenta di impedire l'esecuzione di copie di memoria inutili, ma String.Format finisce comunque con un sovraccarico extra rispetto alla semplice concatenazione.

Se ora facciamo un esempio con qualche stringa in più

"a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString();

abbiamo 6 stringhe con cui iniziare, che saranno le stesse per tutti i casi.
Usando la concatenazione abbiamo anche 4 stringhe intermedie più il risultato finale. Sono quei risultati intermedi che vengono eliminati utilizzando String, Format (o StringBuilder).
Ricorda che per creare ogni stringa intermedia, la precedente deve essere copiata in una nuova posizione di memoria, non è solo l'allocazione della memoria che è potenzialmente lenta.


4
Nitpick. Nella "a" + ... + "b" + ... + "c" + ..., in realtà non avrai 4 stringhe intermedie. Il compilatore genererà una chiamata al metodo statico String.Concat (params string [] values) e verranno tutti concatenati contemporaneamente. Preferirei comunque string.Format per motivi di leggibilità, però.
P Daddy

2

Mi piace String.Format perché può rendere il tuo testo formattato molto più facile da seguire e leggere rispetto alla concatenazione in linea, inoltre è molto più flessibile e ti consente di formattare i tuoi parametri, tuttavia per usi brevi come il tuo non vedo alcun problema di concatenazione.

Per le concatenazioni all'interno di loop o in stringhe di grandi dimensioni dovresti sempre provare a utilizzare la classe StringBuilder.


2

Quell'esempio è probabilmente troppo banale per notare una differenza. In effetti, penso che nella maggior parte dei casi il compilatore possa ottimizzare qualsiasi differenza.

Tuttavia, se dovessi indovinare, darei string.Format()un vantaggio per scenari più complicati. Ma questa è più una sensazione viscerale che è probabile che faccia un lavoro migliore utilizzando un buffer invece di produrre più stringhe immutabili e non sulla base di dati reali.


1

Sono d'accordo con molti punti sopra, un altro punto che credo debba essere menzionato è la manutenibilità del codice. string.Format consente una modifica più semplice del codice.

cioè ho un messaggio "The user is not authorized for location " + locationo "The User is not authorized for location {0}"

se mai volessi cambiare il messaggio per dire: location + " does not allow this User Access"o "{0} does not allow this User Access"

con string.Format tutto quello che devo fare è cambiare la stringa. per la concatenazione devo modificare quel messaggio

se utilizzato in più posti può far risparmiare un sacco di tempo.


1

Avevo l'impressione che string.format fosse più veloce, sembra essere 3 volte più lento in questo test

string concat = "";
        System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch    ();
        sw1.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}","1", "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , i);
        }
        sw1.Stop();
        Response.Write("format: "  + sw1.ElapsedMilliseconds.ToString());
        System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
        sw2.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10" + i;
        }
        sw2.Stop();

string.format ha impiegato 4,6 secondi e quando si usa '+' ci sono voluti 1,6 secondi.


7
Il compilatore riconosce "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"come una stringa letterale, quindi la riga diventa effettivamente quella "12345678910" + ipiù veloce della precedentestring.Format(...)
wertzui

0

string.Format è probabilmente una scelta migliore quando il modello di formato ("C {0}") è archiviato in un file di configurazione (come Web.config / App.config)


0

Ho fatto un po 'di profiling di vari metodi di stringa tra cui string.Format, StringBuilder e concatenazione di stringhe. La concatenazione di stringhe ha quasi sempre sovraperformato gli altri metodi di creazione di stringhe. Quindi, se le prestazioni sono fondamentali, allora è meglio. Tuttavia, se le prestazioni non sono critiche, personalmente trovo che string.Format sia più facile da seguire nel codice. (Ma questa è una ragione soggettiva) StringBuilder, tuttavia, è probabilmente più efficiente per quanto riguarda l'utilizzo della memoria.


0

Preferisco String.Format per quanto riguarda le prestazioni


-1

La concatenazione di stringhe richiede più memoria rispetto a String.Format. Quindi il modo migliore per concatenare le stringhe è usare String.Format o System.Text.StringBuilder Object.

Prendiamo il primo caso: "C" + rowIndex.ToString () Supponiamo che rowIndex sia un tipo di valore, quindi il metodo ToString () deve convertire il valore in String in Box e quindi CLR crea la memoria per la nuova stringa con entrambi i valori inclusi.

Laddove come string.Format si aspetta il parametro dell'oggetto e accetta rowIndex come oggetto e lo converte in stringa internamente fuori campo, ci sarà Boxing ma è intrinseco e inoltre non occuperà la stessa memoria del primo caso.

Per le corde corte non avrà molta importanza immagino ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.