Jaka jest różnica między String.Empty a „” (pusty ciąg)?

288

W .NET, jaka jest różnica między String.Emptyi "", i czy są one wymienne, czy też istnieją jakieś podstawowe odniesienia lub problemy związane z lokalizacją wokół równości, String.Emptyktóre zapewnią, że nie stanowią problemu?

johnc
źródło
2
Prawdziwe pytanie NIE brzmi, co raczej dlaczego . Dlaczego Microsoft wymyślił string.Emptyi jakie było uzasadnienie zadeklarowania go readonlyzamiast const.
user1451111,

Odpowiedzi:

295

W .NET wcześniejszej do wersji 2.0, ""tworzy obiekt natomiast string.Emptystwarza żadnego obiektu ref , co sprawia, że string.Emptybardziej efektywne.

W wersji 2.0 i późniejszych .NET wszystkie wystąpienia ""odnoszą się do tego samego literału łańcuchowego, co oznacza, że ""jest równoważne .Empty, ale wciąż nie tak szybkie jak .Length == 0.

.Length == 0jest najszybszą opcją, ale .Emptyzapewnia nieco czystszy kod.

Aby uzyskać więcej informacji, zobacz specyfikację platformy .NET .

Brian R. Bondy
źródło
91
„” i tak utworzyłby obiekt tylko raz z powodu internowania łańcucha. Zasadniczo kompromisem wydajności są orzeszki ziemne - czytelność jest ważniejsza.
Jon Skeet
12
Ciekawe, że chociaż pytanie nie mówi nic o porównaniu łańcucha do „” lub łańcucha. Puste, aby sprawdzić puste ciągi, wiele osób wydaje się interpretować pytanie w ten sposób ...
peSHIr 15.01.2009
11
Byłbym ostrożny z .Length == 0, ponieważ może wygenerować wyjątek, jeśli twoja zmienna łańcuchowa ma wartość null. Jeśli jednak porównasz to z „”, po prostu poprawnie zwróci false bez wyjątku.
Jeffrey Harmon
11
@JeffreyHarmon: Lub możesz użyć string.IsNullOrEmpty( stringVar ).
Flynn1179,
1
Jeśli ktoś chce zapobiec złym praktykom testowania pustego łańcucha, można włączyć CA1820 w analizie kodu. docs.microsoft.com/visualstudio/code-quality/…
sean
197

jaka jest różnica między String.Empty i "" i czy są one wymienne

string.Emptyjest polem tylko do odczytu, podczas gdy ""jest stałą czasową kompilacji. Miejsca, w których zachowują się inaczej, to:

Domyślna wartość parametru w C # 4.0 lub wyższej

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

Wyrażenie wielkości liter w instrukcji switch

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

    case "":
        break;

}

Podaj argumenty

[Example(String.Empty)]
// Error: An attribute argument must be a constant expression, typeof expression 
//        or array creation expression of an attribute parameter type
Habib
źródło
21
Co ciekawe, nie sądziłem, że to stare pytanie uzyska jakieś istotne nowe informacje. Myliłem się
John
Myślę, że twój przykład # 1 Wartość domyślna parametru w wersji C # 4.0 lub wyższej jest zasadniczo duplikatem twojego przykładu # 3 argumentów atrybutu , ponieważ uważam, że .NET wdraża parametry domyślne do atrybutów. Mówiąc prościej, po prostu nie można umieścić „wartości” (w czasie wykonywania) (w tym przypadku uchwytu instancji dla modułów utrwalających) w metadanych (w czasie kompilacji).
Glenn Slayden
@GlennSlayden, nie zgadzam się z tobą. Inicjalizacja atrybutu różni się od zwykłej inicjalizacji. Jest tak, ponieważ String.Emptyw większości przypadków można przekazać argument jako argument Masz rację, że wszystkie przykłady to pokazują you simply can't put a (run-time) "value" into (compile-time) metadatai właśnie to pokazują.
Sasuke Uchiha
42

Poprzednie odpowiedzi były poprawne dla .NET 1.1 (spójrz na datę posta, do którego prowadzą link: 2003). Począwszy od wersji .NET 2.0 i nowszych, nie ma zasadniczo różnicy. W każdym razie JIT będzie odwoływał się do tego samego obiektu na stercie.

Zgodnie ze specyfikacją C #, sekcja 2.4.4.5: http://msdn.microsoft.com/en-us/library/aa691090(VS.71).aspx

Każdy literał ciągu niekoniecznie powoduje powstanie nowej instancji ciągu. Kiedy dwa lub więcej literałów łańcuchowych, które są równoważne zgodnie z operatorem równości łańcuchów (Rozdział 7.9.7), pojawiają się w tym samym zestawie, te literały łańcuchowe odnoszą się do tej samej instancji łańcucha.

Ktoś nawet wspomina o tym w komentarzach do postu Brada Abrama

Podsumowując, praktyczny wynik „” vs. String.Empty wynosi zero. JIT rozwiąże to na końcu.

Przekonałem się osobiście, że JIT jest znacznie mądrzejszy ode mnie, dlatego staram się nie być zbyt sprytny dzięki takim optymalizacjom mikrokompilatora. JIT będzie się rozwijał dla pętli (), usuwał zbędny kod, metody wbudowane itp. Lepiej i w bardziej odpowiednich momentach, niż ja lub kompilator C # mógł kiedykolwiek przewidzieć. Niech JIT wykona swoją pracę :)

chadmyers
źródło
1
Mała literówka? „W każdym razie JIT będzie odwoływał się do tego samego obiektu w pomocy”. Miałeś na myśli „on the heap”?
Dana
Podobne popularne pytanie stackoverflow.com/questions/263191/...
Michael Freidgeim
36

String.Emptyjest polem tylko do odczytu, podczas gdy ""jest stałą . Oznacza to, że nie można użyć String.Emptyinstrukcji switch, ponieważ nie jest ona stałą.

James Newton-King
źródło
wraz z pojawieniem się defaultsłowa kluczowego możemy zwiększyć czytelność bez, zapobiegać przypadkowej modyfikacji i mieć stałą czasową kompilacji, choć szczerze mówiąc, nadal uważam, że String.Empty jest bardziej czytelny niż domyślny, ale wolniej pisać
Enzoaeneas
18

Kolejna różnica polega na tym, że String.Empty generuje większy kod CIL. Chociaż kod do odwoływania się do „” i String.Empty ma tę samą długość, kompilator nie optymalizuje konkatenacji ciągów (patrz post na blogu Erica Lipperta ) pod kątem argumentów String.Empty. Następujące równoważne funkcje

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

wygeneruj tę 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 
}
Bruno Martinez
źródło
Ważny punkt, ale kto by zrobił coś takiego? Dlaczego powinienem celowo łączyć pusty ciąg znaków z czymś?
Robert S.
@RobertS. Być może drugi ciąg znaków miał osobną funkcję, która została wstawiona. To rzadkie, przyznaję.
Bruno Martinez
3
nie jest tak rzadkie użycie operatora trójskładnikowego:"bar " + (ok ? "" : "error")
symbiont
13

Powyższe odpowiedzi są technicznie poprawne, ale to, czego możesz naprawdę chcieć użyć, dla najlepszej czytelności kodu i najmniejszej szansy na wyjątek to String.IsNullOrEmpty (s)

Eugene Katz
źródło
3
Jeśli chodzi o porównanie równości, całkowicie się zgadzam, ale pytanie dotyczyło również różnicy między tymi dwoma pojęciami, a także porównania
John
1
Uważaj, że „najmniejsza szansa na wyjątek” często oznacza „największą szansę na kontynuację, ale zrobienie czegoś złego”. Na przykład, jeśli masz argument wiersza poleceń, który analizujesz, a ktoś wywołuje twoją aplikację --foo=$BAR, prawdopodobnie chcesz zauważyć różnicę między nimi, zapominając o ustawieniu zmiennej env a tym, że w ogóle nie przekazują flagi. string.IsNullOrEmptyto często zapach kodu, który nie sprawdził poprawności danych wejściowych lub robi dziwne rzeczy. Zasadniczo nie powinieneś akceptować pustych ciągów, gdy naprawdę chcesz użyć nulllub czegoś w rodzaju typu Może / Opcja.
Alastair Maw
11

Zwykle używam String.Emptyraczej niż ""z jednego prostego, ale nieoczywistego powodu: ""i ""NIE są takie same, pierwszy ma w rzeczywistości 16 znaków o zerowej szerokości. Oczywiście żaden kompetentny programista nie wstawi do kodu znaków o zerowej szerokości, ale jeśli się tam dostanie, może to być koszmar konserwacji.

Uwagi:

  • W tym przykładzie użyłem U + FEFF .

  • Nie jestem pewien, czy SO zamierza zjeść te postacie, ale spróbuj sam z jednym z wielu znaków o zerowej szerokości

  • Natknąłem się na to tylko dzięki https://codegolf.stackexchange.com/

The_Lone_Devil
źródło
To zasługuje na więcej pochwał. Widziałem ten problem występujący podczas integracji systemu na różnych platformach.
EvilDr,
8

Użyj String.Emptyraczej niż "".

Jest to więcej niż szybkość użycia pamięci, ale jest to przydatna wskazówka. ""Jest dosłownym tak będzie działać jako dosłowna: od pierwszego użycia i jest on stworzony dla następujących zastosowań zwracana jest jego odniesienie. Tylko jedna instancja ""będzie przechowywana w pamięci bez względu na to, ile razy z niej korzystamy! Nie widzę tu żadnych kar za pamięć. Problem polega na tym, że przy każdym ""użyciu jest wykonywana pętla porównawcza, aby sprawdzić, czy ""jest już w puli wewnętrznej. Z drugiej strony String.Empty jest odniesieniem do ""przechowywanego w strefie pamięci .NET Framework . String.Emptywskazuje ten sam adres pamięci dla aplikacji VB.NET i C #. Po co więc szukać referencji za każdym razem, gdy potrzebujesz ?"" gdy jest gdy ma się taką referencjęString.Empty

Odniesienie: String.Emptyvs""

Mojtaba Rezaeian
źródło
2
Nie było to prawdą od czasu .net 2.0
nelsontruran
6

String.Empty nie tworzy obiektu, podczas gdy „” robi. Różnica, jak wskazano tutaj , jest jednak banalna.

Esteban Araya
źródło
4
Nie jest trywialne, jeśli sprawdzasz ciąg pod kątem ciągu. Puste lub „”. Naprawdę należy używać String.IsNullOrEmpty, jak wskazał Eugene Katz. W przeciwnym razie otrzymasz nieoczekiwane wyniki.
Sigur
6

Wszystkie wystąpienia „” są takie same, dosłownie napisany ciąg znaków (lub powinny być). Więc naprawdę nie będziesz rzucał nowego obiektu na stos za każdym razem, gdy użyjesz „”, ale po prostu utworzysz odniesienie do tego samego, internowanego obiektu. Powiedziawszy to, wolę ciąg. Pusty. Myślę, że dzięki temu kod jest bardziej czytelny.

Jason Jackson
źródło
4
string mystring = "";
ldstr ""

ldstr wypycha odwołanie do nowego obiektu do literału ciągu zapisanego w metadanych.

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

ldsfld wypycha wartość pola statycznego na stos oceny

Zwykle używam String.Emptyzamiast tego, ""ponieważ IMHO jest jaśniejsze i mniej VB.

Salvuccino
źródło
3

Eric Lippert napisał (17 czerwca 2013 r.):
„Pierwszym algorytmem, nad którym pracowałem w kompilatorze C #, był optymalizator obsługujący konkatenacje ciągów. Niestety, nie udało mi się przenieść tych optymalizacji do bazy kodów Roslyn przed wyjazdem; mam nadzieję, że ktoś to zrobi dostać się do tego!

Oto niektóre wyniki Roslyn x64 ze stycznia 2019 r. Pomimo konsensusowych uwag innych odpowiedzi na tej stronie, nie wydaje mi się, aby obecny JIT x64 traktował wszystkie te przypadki identycznie, kiedy wszystko zostało powiedziane i zrobione.

Zauważ jednak w szczególności, że tylko jeden z tych przykładów w rzeczywistości kończy się wywołaniem String.Concati domyślam się, że dzieje się tak z niejasnych powodów poprawności (w przeciwieństwie do nadzoru nad optymalizacją). Inne różnice wydają się trudniejsze do wyjaśnienia.


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


Szczegóły testu

Microsoft (R) Visual C# Compiler version 2.10.0.0 (b9fb1610)
AMD64 Release
[MethodImpl(MethodImplOptions.NoInlining)]
'SuppressJitOptimization' = false
Glenn Slayden
źródło
2

Mówiąc o tym z punktu widzenia Entity Framework: wydaje się, że wersje EF 6.1.3 traktują String.Empty i "" inaczej podczas walidacji.

string.Empty jest traktowany jako wartość zerowa do celów sprawdzania poprawności i zgłosi błąd sprawdzania poprawności, jeśli zostanie użyty w polu Required (przypisanym); gdzie jako „” przejdzie sprawdzanie poprawności i nie zgłosi błędu.

Ten problem można rozwiązać w EF 7+. Odniesienie: - https://github.com/aspnet/EntityFramework/issues/2610 ).

Edycja: [Wymagane (AllowEmptyStrings = true)] rozwiąże ten problem, umożliwiając sprawdzenie poprawności ciągu.Empty.

Anthony
źródło
2

Ponieważ String.Empty nie jest stałą czasową kompilacji, nie można jej użyć jako wartości domyślnej w definicji funkcji.

public void test(int i=0,string s="")
    {
      // Function Body
    }
tsiva124
źródło
1
Wystarczy podsumowanie z tej odpowiedzi: public void test(int i=0, string s=string.Empty) {}nie będzie się kompilować i powie „Domyślna wartość parametru dla 's' musi być stałą czasu kompilacji. Odpowiedź OP działa.
bugybunny
1

Gdy wizualnie skanujesz kod, „” jest pokolorowane w sposób pokolorowany. string.Empty wygląda jak zwykły dostęp do klasy. Podczas szybkiego spojrzenia łatwiej jest dostrzec „” lub zinterpretować znaczenie.

Znajdź łańcuchy (kolorystyka przepełnienia stosu nie pomaga, ale w VS jest to bardziej oczywiste):

var i = 30;
var f = Math.Pi;
var s = "";
var d = 22.2m;
var t = "I am some text";
var e = string.Empty;
K0D4
źródło
2
Masz rację, ale czy programista naprawdę miał na myśli „”? Może chcieli wprowadzić jakąś nieznaną wartość i zapomnieli wrócić? string.Empty ma tę zaletę, że daje pewność, że oryginalny autor naprawdę miał na myśli string.Empty. Wiem, że to drobna sprawa.
mark_h
Ponadto złośliwy (lub nieostrożny) programista może umieścić między cudzysłowami znak o zerowej szerokości zamiast korzystać z odpowiedniej sekwencji ucieczki, takiej jak \u00ad.
Palec
-8

Wszyscy tutaj udzielili dobrych teoretycznych wyjaśnień. Miałem podobne wątpliwości. Więc wypróbowałem na nim podstawowe kodowanie. I znalazłem różnicę. Oto różnica.

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


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

Wydaje się więc, że „Null” oznacza absolutnie nieważne, a „String.Empty” oznacza Zawiera pewną wartość, ale jest pusta.

DeepakTheGeek
źródło
7
Zauważ, że pytanie dotyczy ""kontra string.Empty. nullWspomniano tylko o próbie stwierdzenia, czy ciąg znaków jest pusty .
Palec