W jaki sposób null + true to string?

112

Skoro truenie jest typem string, to jak wygląda null + truełańcuch?

string s = true;  //Cannot implicitly convert type 'bool' to 'string'   
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'

Jaki jest tego powód?

Javed Akram
źródło
27
Robisz coś, co nie ma sensu, a potem nie podoba ci się komunikat, który generuje kompilator? To jest nieprawidłowy kod C # ... co dokładnie miałeś zrobić?
Hogan
19
@Hogan: Kod wydaje się na tyle przypadkowy i akademicki, że można go zadać z czystej ciekawości. Zgaduję ...
BoltClock
8
null + true zwraca 1 w JavaScript.
Fatih Acet

Odpowiedzi:

147

Choć może się to wydawać dziwne, po prostu przestrzega reguł ze specyfikacji języka C #.

Z sekcji 7.3.4:

Operacja w postaci x op y, gdzie op jest operatorem binarnym z możliwością przeciążenia, x jest wyrażeniem typu X, a y jest wyrażeniem typu Y, jest przetwarzana w następujący sposób:

  • Określany jest zbiór kandydujących operatorów zdefiniowanych przez użytkownika dostarczonych przez X i Y dla operatora operacji op (x, y). Zestaw składa się z sumy operatorów kandydatów dostarczonych przez X i operatorów kandydatów dostarczonych przez Y, z których każdy jest określony zgodnie z zasadami określonymi w § 7.3.5. Jeśli X i Y są tego samego typu lub jeśli X i Y pochodzą ze wspólnego typu podstawowego, to współdzielone operatory kandydujące występują tylko raz w połączonym zestawie.
  • Jeśli zbiór kandydujących operatorów zdefiniowanych przez użytkownika nie jest pusty, staje się to zbiorem kandydujących operatorów dla operacji. W przeciwnym razie predefiniowane implementacje operacji operatorów binarnych, w tym ich podniesione formularze, staną się zbiorem operatorów kandydujących do operacji. Predefiniowane implementacje danego operatora są określone w opisie operatora (§7.8 do §7.12).
  • Zasady rozwiązywania przeciążeń z § 7.5.3 są stosowane do zbioru kandydatów na operatorów w celu wybrania najlepszego operatora w odniesieniu do listy argumentów (x, y), a operator ten staje się wynikiem procesu rozwiązywania przeciążenia. Jeśli rozpoznanie przeciążenia nie wybierze jednego najlepszego operatora, wystąpi błąd w czasie powiązania.

A więc przejdźmy przez to po kolei.

X jest tutaj typem zerowym - lub w ogóle nie jest typem, jeśli chcesz o tym myśleć w ten sposób. Nie zapewnia żadnych kandydatów. Y to bool, co nie zapewnia żadnych +operatorów zdefiniowanych przez użytkownika . Więc pierwszy krok nie znajduje operatorów zdefiniowanych przez użytkownika.

Następnie kompilator przechodzi do drugiego punktu, przeglądając predefiniowany operator binarny + implementacje i ich podniesione formularze. Są one wymienione w sekcji 7.8.4 specyfikacji.

Jeśli przejrzysz te predefiniowane operatory, jedynym, który ma zastosowanie, jest string operator +(string x, object y). Zatem zestaw kandydatów ma jeden wpis. To sprawia, że ​​ostatni punktor jest bardzo prosty ... rozwiązanie przeciążenia wybiera ten operator, podając ogólny typ wyrażenia string.

Ciekawostką jest to, że wystąpi to nawet wtedy, gdy dla niewymienionych typów dostępne są inne operatory zdefiniowane przez użytkownika. Na przykład:

// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;

W porządku, ale nie jest używany w przypadku literału o wartości null, ponieważ kompilator nie wie, aby do niego zajrzeć Foo. Wie tylko, aby wziąć pod uwagę, stringponieważ jest to predefiniowany operator wyraźnie wymieniony w specyfikacji. (W rzeczywistości, to nie operator określa typu string ... 1 ) Oznacza to, że będzie to nie kompilacji:

// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;

Inne typy drugiego argumentu będą oczywiście używać innych operatorów:

var x = null + 0; // x is Nullable<int>
var y = null + 0L; // y is Nullable<long>
var z = null + DayOfWeek.Sunday; // z is Nullable<DayOfWeek>

1 Możesz się zastanawiać, dlaczego nie ma operatora string +. To rozsądne pytanie i tylko zgaduję odpowiedź, ale rozważ to wyrażenie:

string x = a + b + c + d;

Gdyby stringnie było specjalnej wielkości liter w kompilatorze C #, to skończyłoby się równie efektywnie:

string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;

To utworzyło dwa niepotrzebne ciągi pośrednie. Jednakże, ponieważ kompilator ma specjalne wsparcie, w rzeczywistości jest w stanie skompilować powyższe jako:

string x = string.Concat(a, b, c, d);

który może utworzyć tylko pojedynczy ciąg o dokładnie odpowiedniej długości, kopiując wszystkie dane dokładnie raz. Miły.

Jon Skeet
źródło
„podając ogólny typ wyrażenia jako łańcuch” Po prostu się nie zgadzam. Błąd wynika z truebraku możliwości zamiany na string. Jeśli wyrażenie było prawidłowe, typ byłby string, ale w tym przypadku niepowodzenie konwersji na łańcuch powoduje, że całe wyrażenie jest błędem, a zatem nie ma typu.
leppie
6
@leppie: Wyrażenie jest prawidłowe, a jego typ to ciąg. Spróbuj "var x = null + true;" - kompiluje się i xjest typu string. Zauważ, że użyty tutaj podpis to string operator+(string, object)- jest konwertowany boolna object(co jest w porządku), a nie na string.
Jon Skeet
Dzięki Jon, zrozum teraz. BTW sekcja 14.7.4 w wersji 2 specyfikacji.
leppie
@leppie: Czy to jest w numeracji ECMA? To zawsze było zupełnie inne :(
Jon Skeet,
47
w 20 minut. Napisał to wszystko w 20 minut. Powinien napisać książkę czy coś… och, poczekaj.
Epaga
44

Powodem jest to, że po wprowadzeniu +w grę wchodzą reguły wiążące operatora C #. Rozważy zestaw +dostępnych operatorów i wybierze najlepsze przeciążenie. Jeden z tych operatorów jest następujący

string operator +(string x, object y)

To przeciążenie jest zgodne z typami argumentów w wyrażeniu null + true. W związku z tym jest wybierany jako operator i jest oceniany jako zasadniczo ((string)null) + trueoceniający do wartości "True".

Sekcja 7.7.4 specyfikacji języka C # zawiera szczegóły dotyczące tego rozwiązania.

JaredPar
źródło
1
Zauważyłem, że wygenerowany IL przechodzi bezpośrednio do wywołania Concat. Myślałem, że zobaczę tam wywołanie funkcji operatora +. Czy byłaby to po prostu optymalizacja wbudowana?
quentin-starin
1
@qstarin wierzę, że powodem jest to, że tak naprawdę nie ma operator+for string. Zamiast tego istnieje tylko w umyśle kompilatora i po prostu tłumaczy to na wezwaniastring.Concat
JaredPar
1
@qstarin @JaredPar: W mojej odpowiedzi zająłem się tym bardziej.
Jon Skeet
11

Kompilator wychodzi na poszukiwanie operatora + (), który może najpierw przyjąć pusty argument. Żaden ze standardowych typów wartości nie kwalifikuje się, null nie jest dla nich prawidłową wartością. Jedynym dopasowaniem jest System.String.operator + (), nie ma dwuznaczności.

Drugi argument tego operatora również jest łańcuchem. To idzie kapooey, nie można implicite przekonwertować bool na string.

Hans Passant
źródło
10

Co ciekawe, używając Reflectora do sprawdzenia, co jest generowane, poniższy kod:

string b = null + true;
Console.WriteLine(b);

jest przekształcany przez kompilator do tego:

Console.WriteLine(true);

Muszę powiedzieć, że powód tej „optymalizacji” jest nieco dziwny i nie rymuje się z wyborem operatora, którego bym oczekiwał.

Ponadto poniższy kod:

var b = null + true; 
var sb = new StringBuilder(b);

jest przekształcany w

string b = true; 
StringBuilder sb = new StringBuilder(b);

gdzie string b = true;faktycznie nie jest akceptowany przez kompilator.

Peter Lillevold
źródło
8

nullzostanie rzutowany na łańcuch pusty i istnieje niejawny konwerter z wartości bool na łańcuch, więc truezostanie on rzutowany na łańcuch, a następnie +zostanie zastosowany operator: wygląda to tak: string str = "" + true.ToString ();

jeśli sprawdzisz to za pomocą Ildasm:

string str = null + true;

jest jak poniżej:

.locals init ([0] string str)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  box        [mscorlib]System.Boolean
  IL_0007:  call       string [mscorlib]System.String::Concat(object)
  IL_000c:  stloc.0
Saeed Amiri
źródło
5
var b = (null + DateTime.Now); // String
var b = (null + 1);            // System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etc
var b = (null + new Object()); // String | same with any ref type

Zwariowany?? Nie, musi być ku temu powód.

Niech ktoś zadzwoni Eric Lippert...

decyklon
źródło
5

Powodem tego jest wygoda (łączenie ciągów jest częstym zadaniem).

Jak powiedział BoltClock, operator „+” jest zdefiniowany dla typów liczbowych, ciągów znaków i może być również zdefiniowany dla naszych własnych typów (przeciążanie operatorów).

Jeśli nie ma przeciążonego operatora „+” w typach argumentów i nie są to typy liczbowe, kompilator domyślnie wykonuje konkatenację ciągów.

Kompilator wstawia wywołanie do, String.Concat(...)gdy łączysz się za pomocą „+”, a implementacja Concat wywołuje ToString na każdym przekazanym obiekcie.

quentin-starin
źródło