Dlaczego ważne jest konkatenowanie ciągów zerowych, ale nie wywoływanie „null.ToString ()”?

126

To jest prawidłowy kod C #

var bob = "abc" + null + null + null + "123";  // abc123

To nie jest prawidłowy kod C #

var wtf = null.ToString(); // compiler error

Dlaczego pierwsze stwierdzenie jest ważne?

superlogiczny
źródło
97
Uważam, że to dziwne, że null.ToString()nadano tobie imię wtf. Dlaczego to cię dziwi? Nie możesz wywołać metody instancji, jeśli nie masz z czego jej wywołać.
BoltClock
11
@BoltClock: Oczywiście możliwe jest wywołanie metody instancji na pustej instancji. Po prostu niemożliwe w C #, ale bardzo ważne na CLR :)
leppie
1
Odpowiedzi dotyczące String.Concat są prawie poprawne. W rzeczywistości konkretnym przykładem w pytaniu jest ciągłe zwijanie , a wartości null w pierwszej linii są usuwane przez kompilator - tj. Nigdy nie są oceniane w czasie wykonywania, ponieważ już nie istnieją - kompilator je usunął. Napisałem mały monolog na temat wszystkich reguł łączenia i ciągłego zwijania ciągów znaków, aby uzyskać więcej informacji: stackoverflow.com/questions/9132338/… .
Chris Shain
77
nie możesz nic dodać do łańcucha, ale nie możesz zrobić łańcucha z niczego.
RGB,
Czy drugie stwierdzenie jest nieważne? class null_extension { String ToString( Object this arg ) { return ToString(arg); } }
ctrl-alt-delor

Odpowiedzi:

163

Powód pierwszego działania:

Z MSDN :

W operacjach łączenia ciągów kompilator C # traktuje ciąg o wartości null tak samo, jak ciąg pusty, ale nie konwertuje wartości oryginalnego ciągu null.

Więcej informacji o operatorze binarnym + :

Operator binarny + wykonuje konkatenację ciągów, gdy jeden lub oba operandy są typu string.

Jeśli operand konkatenacji ciągów ma wartość null, zastępowany jest pusty ciąg. W przeciwnym razie każdy argument niebędący ciągiem jest konwertowany na jego reprezentację w postaci ciągu przez wywołanie ToStringmetody wirtualnej dziedziczonej z typu object.

Jeśli ToStringzwraca null, zastępowany jest pusty ciąg.

Przyczyną błędu w drugim jest:

null (odwołanie w C #) - null słowo kluczowe jest literałem, który reprezentuje odwołanie o wartości null, takie, które nie odwołuje się do żadnego obiektu. null jest domyślną wartością zmiennych typu referencyjnego.

Pranay Rana
źródło
1
Czy to zadziała wtedy? var wtf = ((String)null).ToString();Pracuję ostatnio w Javie, gdzie możliwe jest rzutowanie wartości null, minęło trochę czasu odkąd pracowałem z C #.
Jochem
14
@Jochem: Nadal próbujesz wywołać metodę na obiekcie zerowym, więc domyślam się, że nie. Potraktujcie to jako null.ToString()kontra ToString(null).
Svish
@Svish tak, teraz myślę o tym ponownie, jest to obiekt zerowy, więc masz rację, to nie zadziała. Nie byłoby to również w Javie: wyjątek zerowego wskaźnika. Nieważne. Tnx za odpowiedź! [edytuj: przetestowałem to w Javie: NullPointerException. Z tą różnicą, że z rzutowaniem kompiluje się, a bez rzutowania nie].
Jochem
generalnie nie ma powodu, aby celowo konkatować lub ToString null słowo kluczowe. Jeśli potrzebujesz pustego ciągu, użyj string.Empty. jeśli chcesz sprawdzić, czy zmienna łańcuchowa ma wartość null, możesz użyć (myString == null) lub string.IsNullOrEmpty (myString). Alternatywnie, aby przekształcić pustą zmienną łańcuchową w ciąg znaków.Empty użyj myNewString = (myString == null ?? string.Empty)
csauve
Rzutowanie @Jochem spowoduje kompilację, ale rzeczywiście wyrzuci
wyjątek
108

Ponieważ +operator w C # tłumaczy wewnętrznie String.Concat, co jest metodą statyczną. Ta metoda traktuje nulljak pusty ciąg. Jeśli spojrzysz na źródło String.Concatw Reflektorze, zobaczysz to:

// while looping through the parameters
strArray[i] = (str == null) ? Empty : str;
// then concatenate that string array

(MSDN też o tym wspomina: http://msdn.microsoft.com/en-us/library/k9c94ey1.aspx )

Z drugiej strony ToString()jest to metoda instancji, której nie można wywołać null(do jakiego typu należy użyć null?).

Botz3000
źródło
2
Jednak w klasie String nie ma operatora +. Czy to kompilator tłumaczy na String.Concat?
superlogiczny
@superlogical Tak, właśnie to robi kompilator. Więc w rzeczywistości +operator na łańcuchach w C # jest po prostu cukrem syntaktycznym dla String.Concat.
Botz3000
Dodając do tego: kompilator automatycznie wybiera przeciążenie Concat, które ma największy sens. Dostępne przeciążenia to 1, 2 lub 3 parametry obiektu, 4 parametry obiektu + __arglisti paramswersja tablicy obiektów.
Wormbo
Jaka tablica ciągów jest tam modyfikowana? Czy Concatzawsze tworzy nową tablicę ciągów niezerowych, nawet jeśli otrzyma tablicę ciągów niezerowych, czy też dzieje się coś innego?
supercat
@supercat Zmodyfikowana tablica to nowa tablica, która jest następnie przekazywana do prywatnej metody pomocniczej. Możesz sam spojrzeć na kod za pomocą reflektora.
Botz3000
29

Pierwsza próbka zostanie przetłumaczony na:

var bob = String.Concat("abc123", null, null, null, "abs123");

ConcatWejście metoda sprawdza i tłumaczyć null jako pusty ciąg

Druga próbka zostanie języku:

var wtf = ((object)null).ToString();

Więc tutaj nullzostanie wygenerowany wyjątek referencyjny

Viacheslav Smityukh
źródło
Właściwie AccessViolationExceptionzostanie wyrzucona :)
leppie
Właśnie to zrobiłem :) ((object)null).ToString()=> AccessViolation ((object)1 as string).ToString()=>NullReference
leppie
1
Mam NullReferenceException. DN 4.0
Viacheslav Smityukh
Ciekawy. Kiedy wkładam do ((object)null).ToString()środka, też try/catchdostaję NullReferenceException.
leppie
3
@Ieppie, z wyjątkiem tego, że nie zgłasza AccessViolation (dlaczego miałoby to zrobić?) - tutaj NullReferenceException.
jeroenh
11

Pierwsza część kodu jest traktowana w ten sposób w String.Concat,

który jest tym, co kompilator C # wywołuje podczas dodawania ciągów. " abc" + nullzostanie przetłumaczony na String.Concat("abc", null),

i wewnętrznie, że metoda zastępuje nullsię String.Empty. Dlatego właśnie pierwsza część kodu nie zgłasza żadnego wyjątku. jest po prostu jak

var bob = "abc" + string.Empty + string.Empty + string.Empty + "123";  //abc123

A w drugiej części kodu zgłasza wyjątek, ponieważ „null” nie jest obiektem, słowo kluczowe null jest literałem reprezentującym zerowe odniesienie , które nie odwołuje się do żadnego obiektu. null jest domyślną wartością zmiennych typu referencyjnego.

A „ ToString()” to metoda, która może być wywołana przez instancję obiektu, ale nie przez dowolny literał.

Talha
źródło
3
String.Emptynie jest równe null. W niektórych metodach jest traktowany w ten sam sposób String.Concat. Na przykład, jeśli masz ustawioną zmienną ciągu null, C # nie zastąpi jej, String.Emptyjeśli spróbujesz wywołać na niej metodę.
Botz3000
3
Prawie. nullnie jest traktowany jak String.Emptyprzez C #, jest po prostu traktowany tak, jak w programie String.Concat, co jest wywoływane przez kompilator C # podczas dodawania ciągów. "abc" + nullzostanie przetłumaczony na String.Concat("abc", null)i wewnętrznie, że metoda zastępuje nullz String.Empty. Druga część jest całkowicie poprawna.
Botz3000
9

We frameworku COM, który poprzedzał .net, każda procedura, która otrzymała ciąg znaków, musiała zwolnić go po zakończeniu. Ponieważ puste łańcuchy były bardzo często przekazywane do i z procedur, a próba „zwolnienia” pustego wskaźnika została zdefiniowana jako legalna operacja „nic nie rób”, firma Microsoft zdecydowała się, aby wskaźnik łańcucha zerowego reprezentował pusty ciąg.

Aby zapewnić pewną zgodność z COM, wiele procedur w .net zinterpretuje pusty obiekt jako reprezentację prawną jako pusty ciąg. Z kilkoma drobnymi zmianami .net i jego języki (w szczególności pozwalające członkom instancji na wskazanie „nie wywołuj jako wirtualne”), Microsoft mógł sprawić, że nullobiekty zadeklarowanego typu String zachowywały się jeszcze bardziej jak puste ciągi. Gdyby Microsoft to zrobił, musiałby również wykonać Nullable<T>nieco inną pracę (aby umożliwić - Nullable<String>coś, co i tak powinni zrobić IMHO) i / lub zdefiniować NullableStringtyp, który byłby w większości wymienny String, ale który nie uwzględniałby nulljako prawidłowy pusty ciąg.

W obecnej sytuacji istnieją konteksty, w których a nullzostanie uznany za legalny pusty ciąg, a w innych nie. Sytuacja niezbyt pomocna, ale taka, o której programiści powinni być świadomi. Generalnie, wyrażenia formularza stringValue.someMemberzawodzą, jeśli stringValuetak null, ale większość metod i operatorów frameworka, które akceptują ciągi jako parametry, będzie traktować nulljako pusty łańcuch.

superkat
źródło
5

'+'jest operatorem wrostkowym. Jak każdy operator, tak naprawdę wywołuje metodę. Można sobie wyobrazić wersję bez wrostków"wow".Plus(null) == "wow"

Wykonawca zdecydował się na coś takiego ...

class String
{
  ...
  String Plus(ending)
  {
     if(ending == null) return this;
     ...
  }
} 

Więc ... twój przykład się staje

var bob = "abc".Plus(null).Plus(null).Plus(null).Plus("123");  // abc123

który jest taki sam jak

var bob = "abc".Plus("123");  // abc123

W żadnym momencie null nie staje się łańcuchem. Więc null.ToString()nie jest inaczej null.VoteMyAnswer(). ;)

Nigel Thorne
źródło
Naprawdę bardziej przypomina var bob = Plus(Plus(Plus(Plus("abc",null),null),null),"123");. Przeciążenia operatorów są w istocie metodami statycznymi: msdn.microsoft.com/en-us/library/s53ehcz3(v=VS.71).aspx Gdyby nie były, var bob = null + "abc";a zwłaszcza string bob = null + null;nie byłyby prawidłowe.
Ben Mosher
Masz rację, po prostu czułem, że byłbym zbyt zagubiony, gdybym to wyjaśnił w ten sposób.
Nigel Thorne
3

Chyba dlatego, że jest to literał, który nie odnosi się do żadnego obiektu. ToString()potrzebuje object.

Saeed Neamati
źródło
3

Ktoś powiedział w tym wątku dyskusyjnym, że nie można stworzyć łańcucha z niczego. (co wydaje mi się miłe). Ale tak - możesz :-), jak pokazuje poniższy przykład:

var x = null + (string)null;     
var wtf = x.ToString();

działa dobrze i wcale nie zgłasza wyjątku. Jedyną różnicą jest to, że musisz rzutować jedną z wartości null na łańcuch - jeśli usuniesz rzutowanie (string) , przykład nadal się kompiluje, ale generuje wyjątek w czasie wykonywania: „Operator '+' jest niejednoznaczny dla operandów wpisz „<null>” i „<null>” ”.

NB W powyższym przykładzie kodu wartość x nie jest zerowa, jak można by się spodziewać, jest to w rzeczywistości pusty ciąg po rzutowaniu jednego z operandów na łańcuch.


Innym interesującym faktem jest to, że w C # / .NET sposób nulltraktowania nie zawsze jest taki sam, jeśli wziąć pod uwagę różne typy danych. Na przykład:

int? x = 1;  //  string x = "1";
x = x + null + null;
Console.WriteLine((x==null) ? "<null>" : x.ToString());

Odnośnie pierwszego wiersza fragmentu kodu: jeśli xjest zmienną całkowitą dopuszczającą wartość null (tj. int?) Zawierającą wartość 1, otrzymujesz wynik z <null>powrotem. Jeśli jest to ciąg (jak pokazano w komentarzu) z wartością "1", to "1"zamiast tego wracasz <null>.

NB Również interesujące: jeśli używasz var x = 1;w pierwszej linii, pojawia się błąd wykonania. Czemu? Ponieważ przypisanie zmieni zmienną xw typ danych int, który nie dopuszcza wartości null. Kompilator nie zakłada int?tutaj, a zatem kończy się niepowodzeniem w drugiej linii, w której nulljest dodawany.

Matt
źródło
2

Dodawanie nulldo łańcucha jest po prostu ignorowane. null(w twoim drugim przykładzie) nie jest instancją żadnego obiektu, więc nie ma nawet ToString()metody. To jest po prostu dosłowne.

Thorsten Dittmar
źródło
1

Ponieważ nie ma różnicy między string.Emptyi nullkiedy łączysz ciągi. Możesz również przekazać null do string.Format. Ale próbujesz wywołać metodę on null, co zawsze skutkowałoby a, NullReferenceExceptiona zatem generuje błąd kompilatora.
Jeśli z jakiegoś powodu naprawdę chcesz to zrobić, możesz napisać metodę rozszerzającą, która sprawdza, nulla następnie zwraca string.Empty. Ale takie rozszerzenie powinno być używane tylko wtedy, gdy jest to absolutnie konieczne (moim zdaniem).

Franky
źródło
0

Ogólnie: akceptowanie wartości null jako parametru może, ale nie musi, w zależności od specyfikacji, ale wywołanie metody o wartości null jest zawsze nieprawidłowe.


To i inny temat, dlaczego operandy operatora + mogą mieć wartość null w przypadku łańcuchów. To jest coś w rodzaju VB (przepraszam), aby ułatwić życie programistom lub zakładając, że programista nie może sobie poradzić z zerami. Zupełnie nie zgadzam się z tą specyfikacją. „nieznane” + „cokolwiek” powinno być nadal „nieznane” ...

g.pickardou
źródło