2010-09-27 15 views
12

Eventuali duplicati:
Adding null to a List<bool?> cast as an IList throwing an exception.Non può aggiungere nulla alla lista dei 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); 

Vedi i commenti nel codice di cui sopra.

Comprendo i motivi di ciò (quando si eliminano i generici, il runtime fa il possibile con informazioni limitate). Mi sto solo chiedendo se c'è un modo per aggirare questo, anche se è un po 'un trucco.

Il problema è sorto quando ho provato che la versione generica di una funzione utilizzava la stessa implementazione privata di una versione non generica, quindi posso aggirarla se necessario (ho due implementazioni molto simili), ma ovviamente è meglio se Posso capirlo.

MODIFICA: le ultime due voci che ho sopra NON falliscono come ho detto originariamente. Ma i primi due lo fanno. Ho aggiunto commenti in tal senso nel codice sopra.

+7

Il tuo codice ha funzionato perfettamente per me senza eccezioni quando l'ho provato. –

+1

Posso confermare l'eccezione nel secondo esempio: '.Add ((int?) Null)', .NET 3.5 – Aren

+1

Con 2 positivi e 1 negativo, è ora che tutti inizino a menzionare le versioni del compilatore ecc. –

risposta

5

Per approfondire la discussione nei commenti, sembra che in List<T>.IList.Add a 4.0, non v'è:

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

E 2.0 ha VerifyValueType che controlla semplicemente il metodo IsCompatibleObject:

VerifyValueType(item); 

... 

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

Il quest'ultimo è scritto in modo semplicistico. value non è T (perché null non è lo stesso di Nullable<int>.HasValue = false). Inoltre, come note @LBushkin, typeof (T) .IsValueType restituirà true per Nullable<int> e quindi anche il lato destro verrà valutato come falso.

+0

Credo che il problema qui sia che!! Typeof (T) .IsValueType' restituisce false quando 'T' è' Nullable 'e' valore è T 'valuta anche false. Di conseguenza, il controllo non riesce e non è possibile aggiungere un null tramite questa implementazione. L'implementazione .NET 4.0 si limita a delegare l'implementazione generica 'Lista .Add' che gestisce correttamente questo caso. – LBushkin

+0

@LBushkin, hai ragione, era necessario anche l'indirizzamento. Aggiornerà. –

+0

@Kirk: l'uso di 'degenericed.Add (new Nullable ())' ha esito negativo poiché equivale a 'degenericed.Add ((int?) Null)'. Il risultato finale non è diverso dal passare semplicemente 'null' nel metodo' Aggiungi'. – LukeH

1

Questo funziona in .NET 4.0 con l'introduzione di Covarianza e controvarianza.

Dal momento che non si è in 4.0 (ovviamente dovuto l'errore di runtime) è possibile lavorare intorno ad esso passando default (int) per ottenere un valore nullo

UPDATE: Non ascoltare me di default (int) = 0 NON null. Sono ritardato :(

Questo funziona per nulla:?

degenericed.Add(default(int)); 

La chiamata add funziona correttamente per me però

degenericed.Add(1); 
+0

Non penso che questo abbia nulla a che fare con i cambiamenti di varianza in .NET 4 - è solo un bug nelle versioni precedenti del framework. – LukeH

0

provare a cambiare la linea:

IList degenericed = listONullables; 

da questo:

IList<int?> degenericed = listONullables; 
2

Questo è un bug nei 3.5 quadro (versioni e probabilmente precedenti troppo). Il resto di questa risposta si riferisce a .NET 3.5, anche se i commenti suggeriscono che il bug è stato corretto nella versione 4 del framework ...

Quando si passa un tipo di valore al metodo IList.Add, questo verrà inserito come object a causa dell'interfaccia IList non generica. L'unica eccezione a questa regola è null tipi nullable che vengono convertiti (non in box) in plain null.

Il metodo IList.Add sulle List<T> controlli di classe che il tipo si sta cercando di aggiungere è in realtà un T, ma il controllo di compatibilità non tiene null tipi nullable in considerazione:

quando si passa null , il controllo di compatibilità sa che il tuo elenco è un List<int?> e sa che int? è un tipo di valore, ma - ecco il bug - genera un errore perché anche "sa" che i tipi di valori non possono essere null, ergo il null che hai passato non può essere un int? .

+0

infatti non genera un errore per '1'. –

+0

@Kirk: Oops, corretto! – LukeH