2009-05-13 9 views
204

Właśnie natknąłem się na dziwny błąd:Rodzaje null i operator trójskładnikowy: dlaczego `? 10: zabronione?

private bool GetBoolValue() 
{ 
    //Do some logic and return true or false 
} 

Następnie w innej metody, coś takiego:

int? x = GetBoolValue() ? 10 : null; 

proste, jeśli metoda zwraca true, przypisywanie 10 do Nullable int x. W przeciwnym razie przypisz wartość null do nullable int. Jednak kompilator uskarża się na:

Error 1 Type of conditional expression cannot be determined because there is no implicit conversion between int and <null> .

Czy zwariowałem?

+2

Być może zostanie to poprawione w przyszłej wersji kompilatora, bo tam naprawdę jest niejawna konwersja między „int” i „” i to 'int? –

+0

@bruno, Jak to się różni od powiedzenia "naprawdę jest niejawna konwersja pomiędzy [dowolnego typu] i [dowolnego innego typu] i tym" obiektem "? Czy myślisz, że "poprawią" to również w przyszłej wersji kompilatora? Na przykład: object x = GetBoolValue()? (object) "foo": (object) DateTime.Now; – LukeH

+2

Wyobraź sobie, że kompilator był wystarczająco inteligentny, aby powiedzieć "OK, nie używamy pierwszego typu (int), więc szukamy innego typu, którego możemy użyć dla wyniku tego wyrażenia". Co byś otrzymał, gdybyś wpisał "Console.WriteLine ((Predicate()? 5.6: null) .GetType(). ToString());"? Czy otrzymasz pływak? lub podwójne? lub obiekt? Lub klasa zdefiniowana przez użytkownika z niejawną konwersją z float? W jaki sposób kompilator będzie wiedział, który typ wybrać, ponieważ nie może wybrać tego w wyrażeniu? – mquander

Odpowiedz

336

Kompilator najpierw próbuje ocenić ekspresję prawej:

GetBoolValue() ? 10 : null 

10 jest int dosłowne (nie int?) i null jest , no, null. Nie ma żadnej niejawnej konwersji między tymi dwoma, stąd komunikat o błędzie.

Jeśli zmienisz wyrażenie prawy do jednego z następujących następnie kompiluje ponieważ istnieje niejawna konwersja między int? i null (# 1) oraz między int i int? (# 2, # 3).

GetBoolValue() ? (int?)10 : null // #1 
GetBoolValue() ? 10 : (int?)null // #2 
GetBoolValue() ? 10 : default(int?) // #3 
+6

Możesz również napisać 'new int?()'. – SLaks

+36

Lub jeszcze lepiej IMHO: 'default (int?)' – Lucero

+1

Tak, kompilator wymaga tej wskazówki, zanim będzie ** opakować ** 'int' 10 na' int? '. Zauważ, że inną możliwością byłoby ** box ** 'int' 10 do klasy bazowej lub interfejsu' System.Int32'. Jako przykład 'GetBoolValue()? (IFormattable) 10: null // # 1B' lub 'GetBoolValue()? 10: (IFormattable) null // # 2B' będzie w porządku. Ta możliwość może być powodem, dla którego nie powodują automatycznego owijania nullable. Ponieważ zarówno zawijanie, jak i boksowanie są zwykle _niewielkimi_konwersjami. To jest zarówno 'int? zmienna = 10; 'i' Zmienna IFormattable = 10; 'są legalne (pierwsze z nich to okładki, drugie pola). –

30

Spróbuj tego:

int? x = GetBoolValue() ? 10 : (int?)null; 

zasadzie to, co się dzieje jest to, że operator warunkowy nie jest w stanie określić „typ zwracany” wyrażenia. Ponieważ kompilator domyślnie decyduje, że 10 jest, wówczas decyduje, że typem zwrotu tego wyrażenia będzie również int. Ponieważ int nie może być null (trzeci operand operatora warunkowego), narzeka.

Przesyłając null do Nullable<int>, mówimy wyraźnie kompilatorowi, że typem zwrotu tego wyrażenia będzie Nullable<int>. Równie dobrze mogłeś rzucić 10 do int? i miał taki sam efekt.

+0

Wiem, że mogę obejść to, jestem po prostu ciekawy, dlaczego tak się dzieje? – BFree

3
int? x = GetBoolValue() ? 10 : (int?)null; 

Powodem widać to dlatego, że za kulisami używasz pustych i trzeba powiedzieć, że C# „null” jest zerowa instancja pustych.

+0

Doh! Andrew dostał ich pierwszy. +1 do niego. –

4

Spróbuj jeden z nich:

int? x = GetBoolValue() ? (int?)10 : null; 

int? x = GetBoolValue() ? 10 : (int?)null; 
+0

możemy dodać kolejną: int? x = GetBoolValue()? 10: new int?(); – Eniola

3

Wystarczy dodać explict obsady.

int? x = GetBoolValue() ? 10 : (int?)null; 

jest trójskładnikowy operatora, który zostaje zmieszany - drugi argument jest liczbą całkowitą, a więc jest to trzeci argument exspected być całkowite, także i zerowa nie pasować.

4

Problem polega na tym, że operator trójskładnikowy wywodzi typ na podstawie pierwszego przypisania parametrów ... 10 w tym przypadku, który jest int, a nie interem nullable.

Można mieć więcej szczęścia z:

int? x = GetBoolValue() (int?)10 : null; 
3

Dzieje się tak dlatego, że kompilator określa typ operatora warunkowego przez jego drugi i trzeci operand, a nie przez to, do czego przypisano wynik. Nie ma bezpośredniego rzutowania między liczbą całkowitą a wartością zerową, którą kompilator może użyć do określenia typu.

12

Nawiasem mówiąc, implementacja Microsoft kompilatora C# faktycznie pobiera analizę typu warunkowego błędu operatora w bardzo subtelny i interesujący (do mnie) sposób. Mój artykuł na ten temat to: Type inference woes, part one.

13

Spróbuj tego:

int? result = condition ? 10 : default(int?);

Powiązane problemy