Qual è la differenza tra String.Empty e “” (stringa vuota)?


288

In .NET, qual è la differenza tra String.Emptye "", e sono intercambiabili, o c'è qualche riferimento sottostante o problemi di localizzazione attorno all'uguaglianza che String.Emptygarantiranno che non ci siano problemi?


2
La vera domanda NON è cosa piuttosto perché . Perché Microsoft ha inventato string.Emptye qual è stata la logica alla base della sua dichiarazione readonlyanziché const.
user1451111

Risposte:


294

In .NET precedenti alla versione 2.0, ""crea un oggetto mentre string.Emptycrea nessun oggetto ref , che rende string.Emptypiù efficiente.

Nella versione 2.0 e successive di .NET, tutte le occorrenze di si ""riferiscono alla stessa stringa letterale, il che significa che ""è equivalente a .Empty, ma ancora non così veloce .Length == 0.

.Length == 0è l'opzione più veloce, ma .Emptyrende il codice leggermente più pulito.

Vedere la specifica .NET per ulteriori informazioni .


91
"" avrebbe comunque creato un oggetto una sola volta a causa dell'internazionalizzazione delle stringhe. Fondamentalmente il compromesso delle prestazioni sono le noccioline: la leggibilità è più importante.
Jon Skeet,

12
Interessante il fatto che anche se la domanda non dice nulla sul confronto di una stringa con "" o stringa. Vuoto per verificare la presenza di stringhe vuote, molte persone sembrano interpretare la domanda in questo modo ...
peSHIr,

11
Starei attento con .Length == 0, in quanto può generare un'eccezione se la variabile della stringa è nulla. Se lo controlli con "", però, restituirà correttamente falso senza eccezioni.
Jeffrey Harmon,

11
@JeffreyHarmon: Oppure potresti usare string.IsNullOrEmpty( stringVar ).
Flynn1179,

1
Se qualcuno vuole impedire che le cattive pratiche testino una stringa vuota, è possibile abilitare CA1820 in Code Analysis. docs.microsoft.com/visualstudio/code-quality/…
sean,

197

qual è la differenza tra String.Empty e "", e sono intercambiabili

string.Emptyè un campo di sola lettura mentre ""è una costante di tempo di compilazione. I luoghi in cui si comportano diversamente sono:

Valore del parametro predefinito in C # 4.0 o successivo

void SomeMethod(int ID, string value = string.Empty)
// Error: Default parameter value for 'value' must be a compile-time constant
{
    //... implementation
}

Espressione case nell'istruzione switch

string str = "";
switch(str)
{
    case string.Empty: // Error: A constant value is expected. 
        break;

    case "":
        break;

}

Argomenti dell'attributo

[Example(String.Empty)]
// Error: An attribute argument must be a constant expression, typeof expression 
//        or array creation expression of an attribute parameter type

21
Interessante, non pensavo che questa vecchia domanda avrebbe ricevuto nuove informazioni rilevanti. Ho sbagliato
johnc il

Penso che il valore del parametro predefinito n. 1 di esempio in C # 4.0 o superiore sia essenzialmente un duplicato degli argomenti di attributo del tuo esempio n. 3 , poiché credo che .NET distribuisca i parametri predefiniti negli attributi. Quindi, in sostanza, semplicemente non è possibile inserire un "valore" (run-time) (in questo caso, un handle di istanza , per gli stickler là fuori) in metadati (tempo di compilazione).
Glenn Slayden,

@GlennSlayden, non sono d'accordo con te. L'inizializzazione degli attributi è diversa da una normale inizializzazione. Questo perché String.Emptynella maggior parte dei casi puoi passare come argomento. Hai ragione che tutti gli esempi lo dimostrano you simply can't put a (run-time) "value" into (compile-time) metadatae questo è ciò che gli esempi mirano a mostrare.
Sasuke Uchiha,

42

Le risposte precedenti erano corrette per .NET 1.1 (guarda la data del post che hanno collegato: 2003). A partire da .NET 2.0 e versioni successive, non vi è sostanzialmente alcuna differenza. La JIT finirà comunque per fare riferimento allo stesso oggetto sull'heap.

Secondo la specifica C #, sezione 2.4.4.5: http://msdn.microsoft.com/en-us/library/aa691090(VS.71).aspx

Ogni stringa letterale non comporta necessariamente una nuova istanza di stringa. Quando due o più valori letterali di stringa equivalenti in base all'operatore di uguaglianza di stringa (Sezione 7.9.7) appaiono nello stesso assieme, questi valori letterali di stringa fanno riferimento alla stessa istanza di stringa.

Qualcuno lo menziona persino nei commenti del post di Brad Abram

In sintesi, il risultato pratico di "" vs. String.Empty è zero. Il JIT lo capirà alla fine.

Ho scoperto, personalmente, che JIT è molto più intelligente di me e quindi cerco di non essere troppo intelligente con ottimizzazioni del micro-compilatore del genere. Il JIT si aprirà per () loop, rimuoverà il codice ridondante, i metodi inline, ecc. Meglio e in momenti più appropriati di quanto io o il compilatore C # potremmo mai anticipare prima. Lascia che JIT faccia il suo lavoro :)


1
Piccolo errore di battitura? "La JIT finirà comunque per fare riferimento allo stesso oggetto nella guida." Intendevi "sul mucchio"?
Dana,


36

String.Emptyè un campo di sola lettura mentre ""è una const . Ciò significa che non è possibile utilizzare String.Emptyin un'istruzione switch perché non è una costante.


con l'avvento della defaultparola chiave possiamo promuovere la leggibilità senza, impedire modifiche accidentali e avere una costante di compilazione, anche se ad essere sincero, penso ancora String.Empty è più leggibile del valore predefinito, ma più lento da digitare
Enzoaeneas

18

Un'altra differenza è che String.Empty genera un codice CIL più grande. Mentre il codice di riferimento "" e String.Empty ha la stessa lunghezza, il compilatore non ottimizza la concatenazione di stringhe (vedi il post sul blog di Eric Lippert ) per gli argomenti String.Empty. Le seguenti funzioni equivalenti

string foo()
{
    return "foo" + "";
}
string bar()
{
    return "bar" + string.Empty;
}

genera questo IL

.method private hidebysig instance string foo() cil managed
{
    .maxstack 8
    L_0000: ldstr "foo"
    L_0005: ret 
}
.method private hidebysig instance string bar() cil managed
{
    .maxstack 8
    L_0000: ldstr "bar"
    L_0005: ldsfld string [mscorlib]System.String::Empty
    L_000a: call string [mscorlib]System.String::Concat(string, string)
    L_000f: ret 
}

Punto valido ma chi farebbe una cosa del genere? Perché dovrei concatenare una stringa vuota a qualcosa apposta?
Robert S.

@RobertS. Forse la seconda stringa era in una funzione separata che è stata incorporata. È raro, te lo garantisco.
Bruno Martinez,

3
non è così raro usare l'operatore ternario:"bar " + (ok ? "" : "error")
simbionte

13

Le risposte sopra sono tecnicamente corrette, ma ciò che potresti davvero voler usare, per la migliore leggibilità del codice e la minima possibilità di un'eccezione è String.IsNullOrEmpty (s)


3
In termini di confronto sull'uguaglianza, sono completamente d'accordo, ma la domanda riguardava anche la differenza tra i due concetti e il confronto
johnc

1
Attenzione che "la minima possibilità di un'eccezione" spesso significa "più possibilità di continuare ma fare qualcosa di sbagliato". Ad esempio, se hai un argomento della riga di comando che stai analizzando e qualcuno chiama la tua app --foo=$BAR, probabilmente vorrai individuare la differenza tra loro dimenticando di impostare un var e senza passare la bandiera. string.IsNullOrEmptyè spesso un odore di codice che non hai convalidato correttamente i tuoi input o stai facendo cose strane. Non dovresti generalmente accettare stringhe vuote quando intendi veramente usare null, o qualcosa del tipo Forse / Opzione.
Alastair Maw,

11

Tendo a usarlo String.Emptypiuttosto che ""per un motivo semplice, ma non ovvio: ""e ""NON sono gli stessi, il primo in realtà ha 16 caratteri di larghezza zero. Ovviamente nessuno sviluppatore competente inserirà caratteri e larghezza zero nel loro codice, ma se entrano lì, può essere un incubo per la manutenzione.

Appunti:

  • Ho usato U + FEFF in questo esempio.

  • Non sono sicuro se SO mangerà quei personaggi, ma provalo tu stesso con uno dei tanti caratteri a larghezza zero

  • L'ho scoperto solo grazie a https://codegolf.stackexchange.com/


Questo merita più voti. Ho visto che questo problema si verifica tramite l'integrazione di sistema tra piattaforme.
EvilDr

8

Usa String.Emptypiuttosto che "".

Questo è più per la velocità rispetto all'utilizzo della memoria ma è un suggerimento utile. Il ""è un letterale, quindi agirà come un letterale: al primo utilizzo viene creato e per i seguenti usi viene restituito il riferimento. Solo un'istanza di ""verrà memorizzata, non importa quante volte la usiamo! Non vedo alcuna penalità di memoria qui. Il problema è che ogni volta che ""viene utilizzato, viene eseguito un ciclo di confronto per verificare se ""è già presente nel pool interno. D'altra parte, String.Empty è un riferimento a un ""archiviato nell'area di memoria di .NET Framework . String.Emptypunta allo stesso indirizzo di memoria per le applicazioni VB.NET e C #. Quindi perché cercare un riferimento ogni volta che ti serve "" quando hai quel riferimentoString.Empty?

Riferimento: String.Emptyvs""


2
Questo non è più vero da .net 2.0
nelsontruran,

6

String.Empty non crea un oggetto mentre "" lo fa. La differenza, come sottolineato qui , è comunque banale.


4
Non è banale se si controlla una stringa per string.Empty o "". Si dovrebbe davvero usare String.IsNullOrEmpty come ha sottolineato Eugene Katz. Altrimenti otterrai risultati inaspettati.
Sigur,

6

Tutte le istanze di "" sono le stesse, stringa internata letterale (o dovrebbero essere). Quindi non lancerai un nuovo oggetto sull'heap ogni volta che utilizzi "", ma semplicemente creando un riferimento allo stesso oggetto internato. Detto questo, preferisco string.Empty. Penso che renda il codice più leggibile.



4
string mystring = "";
ldstr ""

ldstr invia un nuovo riferimento all'oggetto a una stringa letterale memorizzata nei metadati.

string mystring = String.Empty;
ldsfld string [mscorlib]System.String::Empty

ldsfld inserisce il valore di un campo statico nello stack di valutazione

Tendo a usarlo String.Emptyinvece ""perché IMHO è più chiaro e meno VB-ish.


3

Eric Lippert ha scritto (17 giugno 2013):
"Il primo algoritmo su cui ho mai lavorato nel compilatore C # era l'ottimizzatore che gestisce le concatenazioni di stringhe. Sfortunatamente non sono riuscito a trasferire queste ottimizzazioni sulla base di codice di Roslyn prima di partire; spero che qualcuno lo farà arrivare a quello! "

Ecco alcuni risultati di Roslyn x64 a gennaio 2019. Nonostante le osservazioni di consenso delle altre risposte in questa pagina, non mi sembra che l'attuale JIT x64 stia trattando tutti questi casi in modo identico, quando tutto è detto e fatto.

Nota in particolare, tuttavia, che solo uno di questi esempi finisce per chiamare String.Concat, e suppongo che ciò sia per oscure ragioni di correttezza (al contrario di una svista di ottimizzazione). Le altre differenze sembrano più difficili da spiegare.


default (String) + {default (String), "", String.Empty}

static String s00() => default(String) + default(String);
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s01() => default(String) + "";
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s02() => default(String) + String.Empty;
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

"" + {default (String), "", String.Empty}

static String s03() => "" + default(String);
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s04() => "" + "";
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s05() => "" + String.Empty;
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

String.Empty + {default (String), "", String.Empty}

static String s06() => String.Empty + default(String);
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

static String s07() => String.Empty + "";
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

static String s08() => String.Empty + String.Empty;
    mov  rcx,[String::Empty]
    mov  rcx,qword ptr [rcx]
    mov  qword ptr [rsp+20h],rcx
    mov  rcx,qword ptr [rsp+20h]
    mov  rdx,qword ptr [rsp+20h]
    call F330CF60                 ; <-- String.Concat
    nop
    add  rsp,28h
    ret


Dettagli del test

Microsoft (R) Visual C# Compiler version 2.10.0.0 (b9fb1610)
AMD64 Release
[MethodImpl(MethodImplOptions.NoInlining)]
'SuppressJitOptimization' = false

2

Venendo a questo dal punto di vista di Entity Framework: le versioni EF 6.1.3 sembrano trattare String.Empty e "" in modo diverso durante la convalida.

string.Empty viene trattato come un valore null ai fini della convalida e genererà un errore di convalida se viene utilizzato in un campo obbligatorio (attribuito); dove "" passerà la convalida e non genera l'errore.

Questo problema può essere risolto in EF 7+. Riferimento: - https://github.com/aspnet/EntityFramework/issues/2610 ).

Modifica: [Obbligatorio (AllowEmptyStrings = true)] risolverà questo problema, consentendo la validazione di string.Empty.


2

Poiché String.Empty non è una costante di compilazione, non è possibile utilizzarlo come valore predefinito nella definizione della funzione.

public void test(int i=0,string s="")
    {
      // Function Body
    }

1
Solo il riassunto di questa risposta: public void test(int i=0, string s=string.Empty) {}non compilerà e dirà "Il valore del parametro predefinito per 's' deve essere una costante di compilazione. La risposta di OP funziona.
bugybunny,

1

Quando si esegue la scansione visiva del codice, "" appare colorato come le stringhe sono colorate. string.Empty sembra un normale accesso ai membri della classe. Durante una rapida occhiata, è più facile individuare "" o intuirne il significato.

Individua le stringhe (la colorazione di overflow dello stack non aiuta esattamente, ma in VS questo è più ovvio):

var i = 30;
var f = Math.Pi;
var s = "";
var d = 22.2m;
var t = "I am some text";
var e = string.Empty;

2
Hai un buon punto, ma lo sviluppatore ha davvero significato ""? Forse intendevano mettere un valore ancora sconosciuto e dimenticare di tornare? string.Empty ha il vantaggio di darti la sicurezza che l'autore originale intendesse davvero string.Empty. È un punto secondario che conosco.
mark_h

Inoltre, uno sviluppatore malintenzionato (o negligente) può inserire un carattere di larghezza zero tra le virgolette invece di utilizzare la sequenza di escape appropriata come \u00ad.
Palec,

-8

Tutti qui hanno fornito alcuni buoni chiarimenti teorici. Avevo un dubbio simile. Quindi ho provato un codice di base su di esso. E ho trovato la differenza. Ecco la differenza

string str=null;
Console.WriteLine(str.Length);  // Exception(NullRefernceException) for pointing to null reference. 


string str = string.Empty;
Console.WriteLine(str.Length);  // 0

Quindi sembra "Null" significa assolutamente nulla e "String.Empty" significa che contiene un qualche tipo di valore, ma è vuoto.


7
Si noti che la domanda riguarda ""versus string.Empty. Solo quando si cerca di dire se la stringa è vuota, è nullstata menzionata.
Palec,
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.