Domyślna inicjalizacja ciągu: NULL lub Empty? [Zamknięte]

133

Zawsze inicjowałem moje ciągi na NULL, myśląc, że NULL oznacza brak wartości, a „” lub String.Empty jest prawidłową wartością. Widziałem ostatnio więcej przykładów kodu, w którym String.Empty jest uważana za wartość domyślną lub nie reprezentuje żadnej wartości. Wydaje mi się to dziwne, ponieważ z nowo dodanymi typami dopuszczającymi wartość null w c # wydaje się, że cofamy się o ciągi, nie używając wartości NULL do reprezentowania `` Brak wartości ''.

Czego używasz jako domyślnego inicjatora i dlaczego?

Edycja: W oparciu o odpowiedzi, kontynuuję moje dalsze przemyślenia

  1. Unikanie obsługi błędów Jeśli wartość nie powinna być zerowa, dlaczego została ustawiona na NULLw pierwszej kolejności? Może lepiej byłoby zidentyfikować błąd w miejscu, w którym występuje, zamiast ukrywać go w pozostałej części kodu?

  2. Unikanie sprawdzania wartości null Jeśli masz dość sprawdzania wartości null w kodzie, czy nie byłoby lepiej abstrakcyjnie sprawdzać wartości null? Być może zawiń (lub rozszerz!) Metody łańcuchowe, aby były NULLbezpieczne? Co się dzieje, jeśli ciągle używasz String.Emptyi zdarza się, że null działa w twoim systemie, czy mimo wszystko zaczniesz dodawać NULLsprawdzenia?

Nie mogę się powstrzymać od powrotu do opinii, że to lenistwo. Każdy DBA spoliczkowałby cię na dziewięć sposobów, gdybyś użył „” zamiast nullw swojej \ jej bazie danych. Myślę, że te same zasady obowiązują w programowaniu i powinien być ktoś, kto uderzy w głowę tych, którzy używają, String.Emptya nie NULLreprezentują żadnej wartości.

Powiązane pytania

vfilby
źródło
„Zdrowy umysł”? To nie może być Dana, którą znam.
vfilby
@Joel, jestem zdumiony tym, jak wiele osób nie ma pojęcia o Zim lub GIR. Jestem również zdumiony niektórymi moimi przyjaciółmi, którzy uważają to za odpychające. Nie mówię, że to czysta dobroć, ale są w tym niesamowite bryłki humoru.
vfilby
Wiem, ale czasami fajnie jest udawać, że jest inaczej.
Dana the Sane
1
Często napotykam ten problem na kolekcjach formularzy MVC lub zmiennych sesji, stwierdziłem, że najbardziej użyteczną rzeczą jest przekonwertowanie wartości null na String.Empty with the ?? skrót, a następnie zastosuj dowolną wymaganą operację na łańcuchu. na przykład. (pozycja ?? String.Empty) .Trim (). ToUpper ()
sonjz
4
to nie jest konstruktywne?
nawfal

Odpowiedzi:

113

+1 za rozróżnienie między „pustym” a NULL. Zgadzam się, że „puste” powinno oznaczać „ważne, ale puste”, a „NULL” powinno oznaczać „nieważne”.

Więc odpowiedziałbym na twoje pytanie w ten sposób:

puste, gdy chcę mieć prawidłową wartość domyślną, która może lub nie może zostać zmieniona, na przykład drugie imię użytkownika.

NULL, gdy jest to błąd, jeśli wynikowy kod nie ustawia jawnie wartości.

Adam Liss
źródło
11
Rozróżnienie między NULL i pustym jest świetne, gdy faktycznie istnieje różnica między nimi. Jest jednak wiele przypadków, w których nie ma różnicy, a zatem posiadanie dwóch sposobów przedstawienia tej samej rzeczy jest zobowiązaniem.
Greg Smalter
6
@Greg: Chociaż zgadzam się, że różnorodność może powodować zamieszanie, może też być wielkim atutem. Prosta, spójna konwencja zapisu „” lub NULL w celu rozróżnienia między prawidłowymi i niepoprawnymi wartościami ułatwi zrozumienie kodu. Dlatego zawsze testuję wartości logiczne za pomocą „if (var)”, wskaźników z „if (var! = NULL)” i liczb całkowitych z „if (var! = 0)” - wszystkie one oznaczają to samo dla kompilatora, ale zawierają dodatkowe informacje, które pomagają biednemu programistowi, który utrzymuje mój kod.
Adam Liss
32

Według MSDN :

Inicjując ciągi z Emptywartością zamiast wartości null, można zmniejszyć prawdopodobieństwo NullReferenceExceptionwystąpienia.

IsNullOrEmpty()Niemniej jednak używanie zawsze jest dobrą praktyką.

Tomalak
źródło
46
To, że zmniejszasz szanse na wyjątek, nie oznacza, że ​​wyjątek nie powinien mieć miejsca. Jeśli twój kod zależy od znajdującej się tam wartości, powinien zgłosić wyjątek!
rmeador
1
Jasne, nie ma argumentów. OTOH, jeśli po prostu dołączasz razem ciągi znaków ... Myślę, że to zależy od stylu kodowania, doświadczenia i sytuacji.
Tomalak
To jest głównie to, co używam, aby rozróżnić, którego użyć.
PositiveGuy
3
Nie zapomnij o IsNullOrWhiteSpace () dla .NET framework 4+
Coops
13

Dlaczego chcesz w ogóle zainicjować swój łańcuch? Nie musisz inicjować zmiennej, gdy ją deklarujesz, i IMO, powinieneś to zrobić tylko wtedy, gdy przypisywana wartość jest prawidłowa w kontekście bloku kodu.

Często to widzę:

string name = null; // or String.Empty
if (condition)
{
  name = "foo";
}
else
{
  name = "bar";
}

return name;

Brak inicjalizacji do wartości null byłby równie skuteczny. Ponadto najczęściej chcesz przypisać wartość. Inicjując wartość null, można potencjalnie przegapić ścieżki kodu, które nie przypisują wartości. Tak jak to:

string name = null; // or String.Empty
if (condition)
{
  name = "foo";
}
else if (othercondition)
{
  name = "bar";
}

return name; //returns null when condition and othercondition are false

Jeśli nie zainicjujesz wartości null, kompilator wygeneruje błąd informujący, że nie wszystkie ścieżki kodu przypisują wartość. Oczywiście to bardzo prosty przykład ...

Matthijs


źródło
W Visual Studio, z którego, jak sądzę, używa prawie każdy programista C #, twoja druga sytuacja (bez = null) wygenerowałaby ostrzeżenie, dokładnie z podanego przez ciebie powodu - nie ma znaczenia, czy domyślna wartość ciągu jest pusta. jeśli nie gwarantujesz przypisania przez każdą ścieżkę kodu, IDE (i / lub przypuszczam, że bazowy kompilator [?]) wygeneruje ostrzeżenie. Chociaż ostrzeżenia nie uniemożliwiają kompilacji, nadal istnieją - pozostawienie tych, które można łatwo rozwiązać, może pomóc zaciemnić inne, które mogą zasługiwać na uwagę programisty
Code Jockey
o ile mi wiadomo, pierwsza sytuacja byłaby całkowicie szczęśliwa bez inicjalizacji nameto null(brak ostrzeżeń), ponieważ każda ścieżka kodu przypisuje wartość name- nie ma potrzeby inicjowania w ogóle
Code Jockey
9

W przypadku większości programów, które w rzeczywistości nie są oprogramowaniem do przetwarzania ciągów, logika programu nie powinna zależeć od zawartości zmiennych łańcuchowych. Ilekroć zobaczę coś takiego w programie:

if (s == "value")

Mam złe przeczucie. Dlaczego w tej metodzie występuje ciąg znaków? Jakie jest ustawienie s? Czy wie, że logika zależy od wartości ciągu? Czy wie, że aby zadziałało, musi to być małe litery? Czy powinienem to naprawić, zmieniając go na użycie String.Compare? Czy powinienem tworzyć Enumi analizować w nim plik ?

Z tej perspektywy można dojść do filozofii kodu, która jest dość prosta: unikasz badania zawartości łańcucha, gdy tylko jest to możliwe. Porównywanie ciągu znaków String.Emptyto tak naprawdę szczególny przypadek porównywania go z literałem: jest to coś, czego należy unikać, chyba że naprawdę musisz.

Wiedząc to, nie mrugam, gdy widzę coś takiego w naszej bazie kodu:

string msg = Validate(item);
if (msg != null)
{
   DisplayErrorMessage(msg);
   return;
}

Wiem, że Validateto nigdy nie wróci String.Empty, ponieważ piszemy lepszy kod niż to.

Oczywiście reszta świata nie działa w ten sposób. Kiedy twój program zajmuje się wprowadzaniem danych przez użytkownika, bazami danych, plikami itd., Musisz wziąć pod uwagę inne filozofie. Tam zadaniem twojego kodu jest narzucenie porządku w chaosie. Częścią tego porządku jest wiedza, kiedy pusty łańcuch powinien oznaczać, String.Emptya kiedy powinien oznaczać null.

(Żeby upewnić się, że nie mówię z siebie, przeszukałem tylko naszą bazę kodów pod kątem `String.IsNullOrEmpty '. Wszystkie 54 wystąpienia tego występują w metodach, które przetwarzają dane wejściowe użytkownika, zwracają wartości ze skryptów Pythona, sprawdzają wartości pobrane z zewnętrzne interfejsy API itp.)

Robert Rossney
źródło
6

W rzeczywistości jest to ziejąca dziura w języku C #. Nie ma możliwości zdefiniowania łańcucha, który nie może mieć wartości null. Powoduje to problemy tak proste, jak ten, który opisujesz, co zmusza programistów do podjęcia decyzji, której nie powinni podejmować, ponieważ w wielu przypadkach NULL i String.Empty oznaczają to samo. To z kolei może później zmusić innych programistów do obsługi zarówno wartości NULL, jak i String.Empty, co jest denerwujące.

Większy problem polega na tym, że bazy danych umożliwiają definiowanie pól, które są mapowane na ciąg C #, ale pola bazy danych można zdefiniować jako NIE NULL. Nie ma więc sposobu, aby dokładnie przedstawić, powiedzmy, pole varchar (100) NOT NULL w SQL Server przy użyciu typu C #.

Inne języki, takie jak Spec #, pozwalają na to.

Moim zdaniem niezdolność C # do zdefiniowania łańcucha, który nie zezwala na null, jest tak samo zła, jak jego poprzednia niezdolność do zdefiniowania int, która dopuszcza null.

Aby w pełni odpowiedzieć na twoje pytanie: zawsze używam pustego ciągu do domyślnej inicjalizacji, ponieważ jest on bardziej podobny do sposobu działania typów danych w bazie danych. (Edycja: To stwierdzenie było bardzo niejasne. Powinno brzmieć: „Używam pustego ciągu do domyślnej inicjalizacji, gdy NULL jest stanem zbędnym, podobnie jak ustawiłem kolumnę bazy danych jako NOT NULL, jeśli NULL byłby stanem zbędnym. , wiele moich kolumn DB jest skonfigurowanych jako NIE NULL, więc kiedy wprowadzam je do ciągu C #, ciąg będzie pusty lub będzie miał wartość, ale nigdy nie będzie NULL. Innymi słowy, inicjalizuję tylko ciąg na NULL jeśli null ma znaczenie inne niż znaczenie String.Empty i uważam, że ten przypadek jest mniej niż powszechny (ale ludzie tutaj podali uzasadnione przykłady tego przypadku). ")

Greg Smalter
źródło
Używanie String.Empty jest tylko podobne do jednego ze sposobów definiowania ciągu bazy danych. Używanie null do reprezentowania żadnej wartości jest znacznie bardziej zgodne z null nvarchar. Myślę, że każdy DBA wart swojej soli uderzyłby cię w głupotę na dziewięć sposobów, gdybyś użył słowa „, aby nie przedstawiać żadnej wartości.
vfilby
Właściwie, Greg, źle to zrozumiałeś. To typy wartości nie dopuszczające wartości null są najmniej „jak działają typy baz danych”, ponieważ nie mogą one nigdy zawierać wartości null, a tym samym nie mogą być mapowane na kolumnę dopuszczającą wartość null. W kontrakcie, każdy ciąg może być mapowany na dowolną kolumnę varchar.
Tor Haugen
Masz rację, moje ostatnie stwierdzenie nie było wystarczająco jasne. W większości przypadków moje kolumny bazy danych NIE mają wartości NULL (ponieważ nie byłoby różnicy między znaczeniem pustego ciągu i NULL), więc staram się zachować podobne łańcuchy, nigdy nie przechowując w nich wartości null, i właśnie to miałem na myśli.
Greg Smalter
5

To zależy.

Czy musisz być w stanie stwierdzić, czy brakuje wartości (czy jest możliwe, że nie zostanie ona zdefiniowana)?

Czy pusty ciąg jest prawidłową wartością do użycia tego ciągu?

Jeśli odpowiedziałeś „tak” na oba, będziesz chciał użyć null. W przeciwnym razie nie możesz odróżnić „bez wartości” od „pustego ciągu znaków”.

Jeśli nie musisz wiedzieć, czy nie ma wartości, pusty łańcuch jest prawdopodobnie bezpieczniejszy, ponieważ pozwala pominąć sprawdzanie null, gdziekolwiek go używasz.

Herms
źródło
3

Ustawiam ją na „” lub null - zawsze sprawdzam za pomocą String.IsNullOrEmpty, więc oba są w porządku.

Ale wewnętrzny maniak we mnie mówi, że powinienem ustawić go na zero, zanim będę miał dla niego odpowiednią wartość ...

Surgical Coder
źródło
3

Zawsze deklaruję ciąg za pomocą string.empty;

Ervin Ter
źródło
2

Czy to możliwe, że jest to technika unikania błędów (zalecana czy nie ...)? Ponieważ „” nadal jest łańcuchem znaków, można by wywołać na nim funkcje łańcuchowe, które spowodowałyby wyjątek, gdyby miał wartość NULL?

Dana the Sane
źródło
1
To jest wymówka, którą zwykle słyszę, po prostu brzmi jak lenistwo. „Nie chcę zawracać sobie głowy sprawdzaniem tej wartości, więc idę na skróty”, tak mi się wydaje.
vfilby
Tak, nie zgadzam się. Mogą istnieć pewne sytuacje, w których zmniejszenie ilości sprawdzanie błędów kod jest ładny, ale wywołania funkcji, które nie mają wpływu nie są największe albo ..
Dana sane
2

Zawsze inicjalizuję je jako NULL.

I zawsze korzystać string.IsNullOrEmpty(someString), aby sprawdzić jego wartość.

Prosty.

Pure.Krome
źródło
1

To zależy od sytuacji. W większości przypadków używam String.Empty, ponieważ nie chcę sprawdzać wartości null za każdym razem, gdy próbuję użyć ciągu. To znacznie upraszcza kod i jest mniej prawdopodobne, że wprowadzisz niechciane awarie NullReferenceException.

Ustawiam ciąg na null tylko wtedy, gdy muszę wiedzieć, czy został ustawiony, czy nie i gdzie pusty ciąg jest prawidłowy, aby go ustawić. W praktyce takie sytuacje są rzadkie.

Rob Prouse
źródło
1

Pusty ciąg to wartość (fragment tekstu, który, nawiasem mówiąc, nie zawiera żadnych liter). Null oznacza brak wartości.

Inicjalizuję zmienne na null, gdy chcę wskazać, że nie wskazują one ani nie zawierają rzeczywistych wartości - gdy intencją jest brak wartości.

yfeldblum
źródło
1

Powtarzając odpowiedź Tomalaka, pamiętaj, że kiedy przypiszesz zmienną łańcuchową do wartości początkowej null, twoja zmienna nie będzie już obiektem typu string; to samo z każdym obiektem w C #. Tak więc, jeśli spróbujesz uzyskać dostęp do jakichkolwiek metod lub właściwości swojej zmiennej i zakładasz, że jest to obiekt ciągu, otrzymasz wyjątek NullReferenceException.

Michael Ritchson
źródło
1

Null należy używać tylko w przypadkach, gdy wartość jest opcjonalna. Jeśli wartość nie jest opcjonalna (np. „Nazwa” lub „Adres”), wówczas wartość nigdy nie powinna być pusta. Dotyczy to baz danych, a także POCO i interfejsu użytkownika. Null oznacza „ta wartość jest opcjonalna i obecnie jej nie ma”.

Jeśli twoje pole nie jest opcjonalne, powinieneś zainicjować je jako pusty ciąg. Zainicjowanie go jako null spowoduje umieszczenie obiektu w nieprawidłowym stanie (nieprawidłowym według własnego modelu danych).

Osobiście wolałbym, aby łańcuchy domyślnie nie dopuszczały wartości null, ale zamiast tego dopuszczały wartość null tylko wtedy, gdy zadeklarujemy „ciąg?”. Chociaż być może nie jest to wykonalne lub logiczne na głębszym poziomie; niepewny.

Dave Cousineau
źródło
0

Łańcuchy nie są typami wartości i nigdy nie będą ;-)

Tor Haugen
źródło
0

Myślę, że nie ma powodu, aby nie używać wartości null dla nieprzypisanej (lub w tym miejscu w przepływie programu) wartości. Jeśli chcesz rozróżnić, jest == null. Jeśli chcesz tylko sprawdzić pewną wartość i nie obchodzi Cię, czy jest to wartość null, czy coś innego, String.Equals („XXX”, MyStringVar) działa dobrze.

Volker
źródło