Skoro true
nie 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?
Skoro true
nie 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?
Odpowiedzi:
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ę, string
ponieważ 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 string
nie 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.
true
braku 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.
x
jest typu string
. Zauważ, że użyty tutaj podpis to string operator+(string, object)
- jest konwertowany bool
na object
(co jest w porządku), a nie na string
.
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) + true
oceniający do wartości "True"
.
Sekcja 7.7.4 specyfikacji języka C # zawiera szczegóły dotyczące tego rozwiązania.
operator+
for string
. Zamiast tego istnieje tylko w umyśle kompilatora i po prostu tłumaczy to na wezwaniastring.Concat
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.
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.
null
zostanie rzutowany na łańcuch pusty i istnieje niejawny konwerter z wartości bool na łańcuch, więc true
zostanie 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
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
...
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.