Jest to dość denerwujące, aby przetestować wszystkie moje struny dla null
przed mogę bezpiecznie stosować metody takie jak ToUpper()
, StartWith()
etc ...
Gdyby domyślną wartością string
był pusty ciąg, nie musiałbym testować i czułbym, że jest bardziej spójny z innymi typami wartości, takimi jak int
lub double
na przykład. Dodatkowo Nullable<String>
miałoby to sens.
Dlaczego więc projektanci C # zdecydowali się użyć null
jako domyślnej wartości ciągów?
Uwaga: odnosi się to do tego pytania , ale bardziej skupia się na tym, dlaczego, a nie co z tym zrobić.
c#
string
default-value
Marcel
źródło
źródło
null
(a także zalecam koncepcyjne traktowanienull
i opróżnianie ciągów jako różnych rzeczy). Wartość null może być gdzieś wynikiem błędu, a pusty łańcuch powinien mieć inne znaczenie.Odpowiedzi:
Ponieważ
string
jest to typ odwołania, a wartością domyślną dla wszystkich typów odniesienia jestnull
.Jest to zgodne z zachowaniem typów referencyjnych. Przed wywołaniem członków ich instancji należy sprawdzić, czy istnieje odwołanie zerowe.
Przypisanie wartości domyślnej do określonego typu odwołania innego niż
null
spowodowałoby niespójność .Nullable<T>
współpracuje z typami wartości. Godny uwagi jest fakt, żeNullable
nie został wprowadzony na oryginalnej platformie .NET, więc byłoby dużo złamanego kodu, gdyby zmienili tę zasadę. ( Dzięki uprzejmości @jcolebrand )źródło
String
, z wyjątkiem (1) zachowania typu wartości z posiadaniem użytecznej wartości domyślnej i (2) niefortunnej dodatkowej warstwy pośredniej boksowania za każdym razem, gdy zostanie on oddany doObject
. Biorąc pod uwagę, że reprezentacja stosustring
jest unikalna, specjalne traktowanie w celu uniknięcia dodatkowego boksu nie byłoby zbyt dużym wysiłkiem (w rzeczywistości możliwość określenia niestandardowych zachowań bokserskich byłaby dobra dla innych typów).Habib ma rację - ponieważ
string
jest to typ odniesienia.Ale co ważniejsze, nie musisz sprawdzać za
null
każdym razem, gdy go używasz. Prawdopodobnie powinieneś rzucić a,ArgumentNullException
jeśli ktoś przejdzie twoją funkcjęnull
referencję, .Oto rzecz - framework i tak rzuciłby
NullReferenceException
za ciebie, jeśli spróbujesz wywołać.ToUpper()
ciąg. Pamiętaj, że ten przypadek nadal może się zdarzyć, nawet jeśli przetestujesz argumenty,null
ponieważ dowolna właściwość lub metoda obiektów przekazanych do funkcji, ponieważ parametry mogą oceniaćnull
.To powiedziawszy, sprawdzanie pustych ciągów lub wartości zerowych jest powszechną czynnością, więc zapewniają
String.IsNullOrEmpty()
iString.IsNullOrWhiteSpace()
tylko w tym celu.źródło
NullReferenceException
sam rzucać ( msdn.microsoft.com/en-us/library/ms173163.aspx ); rzucasz,ArgumentNullException
jeśli twoja metoda nie może zaakceptować wartości zerowej. Ponadto NullRef są zwykle jednym z trudniejszych wyjątków od diagnoz, gdy naprawiasz problemy, więc nie sądzę, aby zalecenie, aby nie sprawdzać null, jest bardzo dobre.ArgumentNullException
ma tę dodatkową zaletę, że może podać nazwę parametru. Podczas debugowania oszczędza to ... err, sekundy. Ale ważne sekundy.Możesz napisać metodę rozszerzenia (na ile jest warta):
Teraz działa to bezpiecznie:
źródło
.EmptyNull()
, dlaczego po prostu nie użyć(str ?? "")
tego, gdzie jest to potrzebne? To powiedziawszy, zgadzam się z sentymentem wyrażonym w komentarzu @ DaveMarkle: prawdopodobnie nie powinieneś.null
iString.Empty
są koncepcyjnie różne i nie można traktować jednego tak samo jak drugiego.Można również użyć następujących, począwszy od C # 6.0
Wynik ciągu będzie pusty.
źródło
public string Name { get; set; } = string.Empty;
Puste łańcuchy i wartości null są zasadniczo różne. Wartość null to brak wartości, a pusty ciąg to wartość, która jest pusta.
Język programowania przyjmujący założenia dotyczące „wartości” zmiennej, w tym przypadku pustego łańcucha, będzie tak dobry, jak inicjowanie łańcucha z jakąkolwiek inną wartością, która nie spowoduje problemu z zerowym odwołaniem.
Ponadto, jeśli przekażesz uchwyt tej zmiennej ciągowej do innych części aplikacji, wówczas kod ten nie będzie miał możliwości sprawdzenia, czy celowo przekazałeś pustą wartość, czy zapomniałeś wypełnić wartość tej zmiennej.
Innym przypadkiem, w którym może to być problem, jest to, że ciąg znaków jest wartością zwracaną przez jakąś funkcję. Ponieważ ciąg znaków jest typem referencyjnym i technicznie może mieć zarówno wartość pustą, jak i pustą, dlatego funkcja może również zwrócić wartość zerową lub pustą (nic nie stoi na przeszkodzie, aby to zrobić). Teraz, ponieważ istnieją 2 pojęcia „braku wartości”, tj. Pusty ciąg i zero, cały kod, który korzysta z tej funkcji, będzie musiał wykonać 2 kontrole. Jeden za pusty, a drugi za zerowy.
Krótko mówiąc, zawsze dobrze jest mieć tylko 1 reprezentację dla jednego stanu. Aby uzyskać szerszą dyskusję na temat wartości pustych i zerowych, zobacz poniższe linki.
/software/32578/sql-empty-string-vs-null-value
NULL vs Pusty w przypadku wprowadzania danych przez użytkownika
źródło
null
reprezentowałby pusty łańcuch. Istnieje wiele sposobów .net mógł zaimplementować podobną semantykę, gdyby tak postanowili, szczególnie biorąc pod uwagę, żeString
ma szereg cech, które czynią go unikalnym typem [np. To i dwa typy tablic są jedynymi typami, których alokacja rozmiar nie jest stały].Podstawowym powodem / problemem jest to, że projektanci specyfikacji CLS (która definiuje sposób interakcji języków z .net) nie zdefiniowali sposobu, w jaki członkowie klasy mogliby określić, że muszą być wywoływani bezpośrednio, a nie za pośrednictwem
callvirt
, bez wykonującego kontrola zerowego odniesienia; nie dostarczył też wielu definicji struktur, które nie podlegałyby „normalnemu” boksu.Gdyby specyfikacja CLS zdefiniowała takie środki, to .net byłby w stanie konsekwentnie podążać za wyprzedzeniem ustalonym przez Common Object Model (COM), zgodnie z którym odwołanie do ciągu zerowego traktowano semantycznie jako równoważne pustemu ciągowi, a dla innych zdefiniowane przez użytkownika niezmienne typy klas, które powinny mieć semantykę wartości, aby podobnie definiować wartości domyślne. Zasadniczo to, co by się stało, byłoby dla każdego członka
String
, np.Length
Napisane jako coś w rodzaju[InvokableOnNull()] int String Length { get { if (this==null) return 0; else return _Length;} }
. Takie podejście zapewniłoby bardzo dobrą semantykę dla rzeczy, które powinny zachowywać się jak wartości, ale z powodu problemów z implementacją muszą być przechowywane na stercie. Największą trudnością przy takim podejściu jest to, że semantyka konwersji między takimi typamiObject
może być nieco mętna.Alternatywnym podejściem byłoby zezwolenie na definicję specjalnych typów struktur, które nie dziedziczyły,
Object
ale zamiast tego miały niestandardowe operacje boksu i rozpakowania (które konwertowałyby na / z jakiegoś innego typu klasy). Przy takim podejściu istniałby typ klasy,NullableString
który zachowuje się jak ciąg znaków, oraz niestandardowy typ strukturyString
, który przechowywałby pojedyncze prywatne poleValue
typuString
. Próba konwersjiString
naNullableString
lubObject
zwróci,Value
jeśli nie jest zerowa lubString.Empty
jeśli jest zerowa. PróbaString
użycia rzutowania na odwołanie inne niż null doNullableString
instancji spowoduje zapisanie odwołania wValue
(być może przechowywanie wartości null, jeśli długość wynosi zero); rzutowanie dowolnego innego odwołania spowodowałoby wyjątek.Chociaż ciągi muszą być przechowywane na stercie, nie ma koncepcyjnego powodu, dla którego nie powinny zachowywać się jak typy wartości o wartości domyślnej innej niż null. Przechowywanie ich jako „normalnej” struktury, która zawierała odniesienie, byłoby skuteczne w przypadku kodu, który używał ich jako „łańcucha” typu, ale dodałoby dodatkową warstwę pośredniczości i nieefektywności podczas rzutowania na „obiekt”. Chociaż nie przewiduję dodania żadnej z powyższych funkcji w późniejszym terminie, być może projektanci przyszłych ram mogą rozważyć ich włączenie.
źródło
Nullable<>
, ciągi znaków mogą zostać ponownie zaimplementowane jako typy wartości; Nie mogę mówić o kosztach i korzyściach takiego wyboru.string
jest bardziej wydajny niżNullable<string>
byłby, konieczność zastosowania metody „bardziej wydajnej” jest bardziej uciążliwa niż możliwość zastosowania tego samego podejścia dla wszystkich wartości dopuszczalnych zerowych danych.Ponieważ zmienna łańcuchowa jest odwołaniem , a nie instancją .
Domyślnie zainicjowanie go do Opróżnij byłoby możliwe, ale wprowadziłoby wiele niespójności na całym forum.
źródło
string
że musiałby to być typ odniesienia. Oczywiście, rzeczywiste znaki, które składają się na ciąg, z pewnością muszą być przechowywane na stercie, ale biorąc pod uwagę ilość dedykowanego wsparcia, jakie ciągi mają już w CLR, nie byłoby rozciągnięciem miećSystem.String
typ wartości z wartością pojedyncze prywatne poleValue
typuHeapString
. To pole byłoby typem referencyjnym i domyślnie taknull
, aleString
struktura, którejValue
pole było puste, zachowywałaby się jak pusty ciąg. Jedyną wadą tego podejścia byłoby ...String
naObject
to, przy braku kodu specjalnego przypadku w środowisku wykonawczym, spowoduje utworzenieString
instancji pudełkowej na stercie, zamiast po prostu kopiowania odwołania do plikuHeapString
..Length
itp., Tak aby instancje, które przechowują odwołanie zerowe nie będzie próbowało wyłuskać go, ale zachowa się odpowiednio do pustego ciągu. Czy ramy byłoby lepiej lub gorzej zestring
realizowane w ten sposób, jeśli ktoś chciałdefault(string)
być pusty ciąg ...string
bycie opakowaniem typu wartości w polu typu odniesienia byłoby podejściem, które wymagałoby najmniejszych zmian w innych częściach .net [w rzeczywistości, gdyby ktoś był skłonny zaakceptować konwersję z,String
abyObject
utworzyć dodatkowy element w pudełku, można po prostuString
być zwykłą strukturą z polem typu,Char[]
którego nigdy nie ujawnił]. Wydaje mi się, że posiadanieHeapString
typu byłoby prawdopodobnie lepsze, ale pod pewnymi względami ciąg wartości typu trzymający aChar[]
byłby prostszy.Ponieważ ciągi znaków są typami referencyjnymi, typy referencyjne mają wartość domyślną to
null
. Zmienne typów referencji przechowują odniesienia do rzeczywistych danych.Użyjmy
default
słowa kluczowego w tym przypadku;str
jest astring
, więc jest to typ odniesienia , więc wartością domyślną jestnull
.str
jestint
, więc jest to typ wartości , więc wartością domyślną jestzero
.źródło
Źle! Zmiana wartości domyślnej nie zmienia faktu, że jest to typ referencji i ktoś nadal może jawnie ustawić referencję na
null
.Prawda Bardziej sensowne byłoby, aby nie zezwalać
null
na żadne typy referencji, zamiast tego wymagaćNullable<TheRefType>
tej funkcji.Spójność z innymi typami referencyjnymi. Teraz,
null
po co w ogóle pozwalać na typy referencyjne? Prawdopodobnie dlatego, że wydaje się C, nawet jeśli jest to wątpliwa decyzja projektowa w języku, który również zapewniaNullable
.źródło
Być może, jeśli użyjesz
??
operatora podczas przypisywania zmiennej łańcuchowej, może ci to pomóc.źródło
String jest niezmiennym obiektem, co oznacza, że po podaniu wartości stara wartość nie jest usuwana z pamięci, ale pozostaje w starej lokalizacji, a nowa wartość jest umieszczana w nowej lokalizacji. Więc jeśli wartość domyślna
String a
byłoString.Empty
, byłoby to zmarnowaćString.Empty
blok w pamięci, gdy otrzymałby swoją pierwszą wartość.Chociaż wydaje się to niewielkie, może stać się problemem podczas inicjowania dużej tablicy ciągów z domyślnymi wartościami
String.Empty
. Oczywiście zawsze możesz użyć zmiennejStringBuilder
klasy, jeśli będzie to stanowić problem.źródło
String.Empty
. Czy się mylę?string
była pustym łańcuchem, jest zezwolenie na zero bitów jako reprezentację pustego łańcucha. Można to osiągnąć na wiele sposobów, ale nie sądzę, aby jakikolwiek wymagał inicjalizacji odniesieńString.Empty
.String.Empty
lub""
.string
lokalizacji pamięci wymagałaby również zmiany zachowania inicjalizacji wszystkich struktur lub klas, które mają pola typustring
. Oznaczałoby to dość dużą zmianę w projekcie .net, który obecnie oczekuje, że zainicjuje zero dowolnego typu, nawet nie myśląc o tym, co to jest, z wyjątkiem jego całkowitego rozmiaru.Ponieważ ciąg znaków jest typem odniesienia, a domyślna wartość dla typu odwołania jest pusta.
źródło
Być może
string
słowo kluczowe wprawiło Cię w zakłopotanie, ponieważ wygląda dokładnie tak samo, jak każda inna deklaracja typu wartości , ale tak naprawdę jest to alias doSystem.String
wyjaśnienia w tym pytaniu .Również ciemnoniebieski kolor w programie Visual Studio i mała pierwsza litera mogą wprowadzać w błąd, myśląc, że to
struct
.źródło
object
słowa kluczowego? Chociaż wprawdzie jest to znacznie mniej używane niżstring
.int
alias dlaSystem.Int32
. O co ci chodzi? :)System.Int32
toStruct
ma zatem wartość domyślną, podczas gdySystem.String
jestClass
o wskaźnik o wartości domyślnejnull
. Są wizualnie przedstawione w tej samej czcionce / kolorze. Bez wiedzy można myśleć, że działają w ten sam sposób (= mając wartość domyślną). Moja odpowiedź została napisana wTypy dopuszczające wartości zerowe pojawiły się dopiero w wersji 2.0.
Gdyby na początku języka utworzono typy zerowalne, to łańcuch nie byłby zerowy i ciąg? byłby zerowy. Ale nie mogli tego zrobić, aby zachować zgodność wsteczną.
Wiele osób mówi o typie ref lub ref, ale łańcuch znaków jest wyjątkowy i można by znaleźć rozwiązania, które by to umożliwiały.
źródło