2010-09-27 9 views
12

Powiel możliwe:
Adding null to a List<bool?> cast as an IList throwing an exception.Nie można dodać do listy zerowa nullables

List<int?> listONullables = new List<int?>(); 
IList degenericed = listONullables; 

// This works fine 
listONullables.Add(null); 

// Run time exception: 
// "The value "" is not of type "System.Nullable`1[System.Int32]" 
// and cannot be used in this generic collection. Parameter name: value" 
degenericed.Add(null); 

// Also does not work. Same exception 
degenericed.Add((int?)null); 

// Also does not work 
// EDIT: I was mistaken, this does work 
degenericed.Add((int?)1); 

// Also does not work 
// EDIT: I was mistaken, this does work 
degenericed.Add(1); 

Zobacz komentarze w powyższym kodzie.

W pewnym sensie rozumiem, dlaczego tak się dzieje (kiedy odrzuca się generyczne środowisko wykonawcze, robi to, co jest w stanie, przy ograniczonych informacjach). Zastanawiam się, czy jest na to sposób, nawet jeśli to trochę hack.

Problem powstał, gdy próbowałem, aby ogólna wersja funkcji korzystała z tej samej implementacji prywatnej, co wersja ogólna, więc mogę obejść ją w razie potrzeby (mam dwie bardzo podobne implementacje), ale oczywiście lepiej, jeśli Rozumiem to.

EDYCJA: Dwa ostatnie wpisy, które mam powyżej, NIE zawodzą, tak jak początkowo powiedziałem. Ale pierwsze dwie. Dodałem komentarze do tego efektu w powyższym kodzie.

+7

Twój kod działał idealnie bez wyjątku, kiedy go wypróbowałem. –

+1

Mogę potwierdzić wyjątek w drugim przykładzie: '.Add ((int?) Null)', .NET 3.5 – Aren

+1

Z 2 pozytywami i 1 negatywem, czas, aby wszyscy zaczęli wymieniać wersje kompilatorów itp. –

Odpowiedz

5

Aby rozwinąć dyskusji w komentarzach, wydaje się, że w List<T>.IList.Add w 4,0 istnieje:

ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item); 
try 
{ 
    this.Add((T) item); 
} 
catch (InvalidCastException) 
{ 
    ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T)); 
} 

i 2,0 ma VerifyValueType który po prostu sprawdza metodę IsCompatibleObject:

VerifyValueType(item); 

... 

private static bool IsCompatibleObject(object value) { 
    if((value is T) || (value == null && !typeof(T).IsValueType)) { 
     return true; 
    } 
    return false; 
} 

drugi jest napisany w uproszczony sposób. value to nie T (ponieważ wartość null nie jest taka sama jak Nullable<int>.HasValue = false). Ponadto, jak zauważa @LBushkin, typeof (T) .IsValueType zwróci true dla Nullable<int>, a więc prawa strona również ocenia wartość false.

+0

Wierzę, że problem polega na tym, że '! Typeof (T) .IsValueType' zwraca wartość false, gdy' T' jest 'Nullable ', a 'wartość to T 'również ocenia fałszywość. W związku z tym sprawdzanie kończy się niepowodzeniem i nie można dodać wartości zerowej za pomocą tej implementacji. Implementacja .NET 4.0 po prostu przekazuje do ogólnej implementacji 'List .Add', która poprawnie obsługuje ten przypadek. – LBushkin

+0

@LBushkin, masz rację, to również wymagało adresowania. Zaktualizuję. –

+0

@Kirk: Użycie 'degenericed.Add (new Nullable ()) również kończy się niepowodzeniem, ponieważ jest równoważne' degenericed.Add ((int?) Null) '. Wynik końcowy nie różni się od przekazywania zwykłego 'null' do metody' Add'. – LukeH

1

Działa to w .NET 4.0 z wprowadzeniem kowariancji i kontrawariancji.

Ponieważ nie są w 4,0 (oczywiście z powodu błędu wykonawczego) można obejść poprzez przepuszczenie domyślną (int), aby uzyskać wartość null

UPDATE: Nie słuchaj mnie domyślna (int) = 0 NOT null. Jestem opóźniony :(

Działa to na wartość null:

degenericed.Add(default(int)); 

Wezwanie dodatek działa prawidłowo dla mnie chociaż

degenericed.Add(1); 
+0

Nie sądzę, że ma to coś wspólnego ze zmianami wariancji w .NET 4 - to tylko błąd w starszych wersjach tego frameworka. – LukeH

0

Spróbuj zmienić linię:

IList degenericed = listONullables; 

przez to:

IList<int?> degenericed = listONullables; 
2

Jest to błąd w 3.5 Framework (i wcześniejszych wersjach pewnie też). Reszta tej odpowiedzi dotyczy .NET 3.5, chociaż komentarze sugerują, że błąd został naprawiony w wersji 4 architektury ...

Po przekazaniu wartości typu do metody IList.Add będzie ona oznaczona jako object, ponieważ interfejs IList nie jest generyczny. Jedynym wyjątkiem od tej reguły są typy zerowalne, które są konwertowane (nie w ramkach) na zwykły null.

Sposób IList.Add na kontrole klasy List<T> że rodzaj próbujesz dodać jest rzeczywiście T, lecz sprawdzenie kompatybilności nie bierze null zerowalne typów uwzględnieniu:

kiedy przechodzą null , sprawdzanie zgodności wie, że twoja lista jest List<int?> i wie, że int? jest typem wartości, ale - tutaj jest błąd - zgłasza błąd, ponieważ "wie", że typy wartości nie mogą być null, ergo null że zdałeś nie może być int? .

+0

w rzeczywistości nie rzuca błędu dla "1". –

+0

@Kirk: Ups, naprawiony! – LukeH

Powiązane problemy