Ci sono due problemi qui: 1) test per vedere se un Tipo è nullable; e 2) test per vedere se un oggetto rappresenta un Tipo nullable.
Per il numero 1 (test di un tipo), ecco una soluzione che ho usato nei miei sistemi: TypeIsNullable-check solution
Per il numero 2 (test di un oggetto), la soluzione di Dean Chalk sopra funziona per i tipi di valore, ma non funziona per i tipi di riferimento, poiché l'utilizzo del sovraccarico <T> restituisce sempre false. Poiché i tipi di riferimento sono intrinsecamente nulla, il test di un tipo di riferimento dovrebbe sempre restituire true. Si prega di vedere la nota [A proposito di "nullability"] sotto per una spiegazione di queste semantiche. Quindi, ecco la mia modifica all'approccio di Dean:
public static bool IsObjectNullable<T>(T obj)
{
// If the parameter-Type is a reference type, or if the parameter is null, then the object is always nullable
if (!typeof(T).IsValueType || obj == null)
return true;
// Since the object passed is a ValueType, and it is not null, it cannot be a nullable object
return false;
}
public static bool IsObjectNullable<T>(T? obj) where T : struct
{
// Always return true, since the object-type passed is guaranteed by the compiler to always be nullable
return true;
}
Ed ecco la mia modifica al codice di test client per la soluzione sopra:
int a = 123;
int? b = null;
object c = new object();
object d = null;
int? e = 456;
var f = (int?)789;
string g = "something";
bool isnullable = IsObjectNullable(a); // false
isnullable = IsObjectNullable(b); // true
isnullable = IsObjectNullable(c); // true
isnullable = IsObjectNullable(d); // true
isnullable = IsObjectNullable(e); // true
isnullable = IsObjectNullable(f); // true
isnullable = IsObjectNullable(g); // true
Il motivo per cui ho modificato l'approccio di Dean in IsObjectNullable <T> (T t) è che il suo approccio originale ha sempre restituito false per un tipo di riferimento. Poiché un metodo come IsObjectNullable dovrebbe essere in grado di gestire i valori del tipo di riferimento e poiché tutti i tipi di riferimento sono intrinsecamente nulli, quindi se viene passato un tipo di riferimento o un valore nullo, il metodo dovrebbe sempre restituire true.
I due metodi precedenti potrebbero essere sostituiti con il seguente metodo singolo e ottenere lo stesso risultato:
public static bool IsObjectNullable<T>(T obj)
{
Type argType = typeof(T);
if (!argType.IsValueType || obj == null)
return true;
return argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(Nullable<>);
}
Tuttavia, il problema con quest'ultimo approccio a metodo singolo è che le prestazioni risentono dell'utilizzo di un parametro Nullable <T>. Ci vuole molto più tempo del processore per eseguire l'ultima riga di questo singolo metodo di quanto non consenta al compilatore di scegliere il secondo sovraccarico del metodo mostrato in precedenza quando nella chiamata IsObjectNullable viene utilizzato un parametro di tipo Nullable <T>. Pertanto, la soluzione ottimale è utilizzare l'approccio a due metodi qui illustrato.
CAVEAT: questo metodo funziona in modo affidabile solo se chiamato usando il riferimento all'oggetto originale o una copia esatta, come mostrato negli esempi. Tuttavia, se un oggetto nullable è inscatolato in un altro tipo (come oggetto, ecc.) Invece di rimanere nella sua forma Nullable <> originale, questo metodo non funzionerà in modo affidabile. Se il codice che chiama questo metodo non utilizza il riferimento all'oggetto originale, senza scatola o una copia esatta, non può determinare in modo affidabile la nullità dell'oggetto utilizzando questo metodo.
Nella maggior parte degli scenari di codifica, per determinare la nullità si deve invece fare affidamento sul test del Tipo dell'oggetto originale, non sul suo riferimento (ad esempio, il codice deve avere accesso al Tipo originale dell'oggetto per determinare il nullabilità). In questi casi più comuni, IsTypeNullable (vedi link) è un metodo affidabile per determinare la nullità.
PS - A proposito di "nullability"
Dovrei ripetere una dichiarazione sulla nullità che ho fatto in un post separato, che si applica direttamente per affrontare correttamente questo argomento. Cioè, credo che il focus della discussione qui non dovrebbe essere come verificare se un oggetto è un tipo Nullable generico, ma piuttosto se si può assegnare un valore null a un oggetto del suo tipo. In altre parole, penso che dovremmo determinare se un tipo di oggetto è nullable, non se sia Nullable. La differenza sta nella semantica, vale a dire i motivi pratici per determinare la nullabilità, che di solito è tutto ciò che conta.
In un sistema che utilizza oggetti con tipi probabilmente sconosciuti fino al runtime (servizi Web, chiamate remote, database, feed, ecc.), Un requisito comune è determinare se un oggetto null può essere assegnato all'oggetto o se l'oggetto potrebbe contenere un null. L'esecuzione di tali operazioni su tipi non annullabili produrrà probabilmente errori, in genere eccezioni, che sono molto costosi sia in termini di prestazioni che di requisiti di codifica. Per adottare l'approccio altamente preferito di evitare proattivamente tali problemi, è necessario determinare se un oggetto di un tipo arbitrario è in grado di contenere un valore nullo; vale a dire, se è generalmente "nullable".
In un senso molto pratico e tipico, la nullabilità in termini di .NET non implica necessariamente che il Tipo di un oggetto sia una forma di Nullable. In molti casi, infatti, gli oggetti hanno tipi di riferimento, possono contenere un valore nullo e quindi sono tutti nullable; nessuno di questi ha un tipo Nullable. Pertanto, per scopi pratici nella maggior parte degli scenari, i test dovrebbero essere eseguiti per il concetto generale di nullability, rispetto al concetto dipendente dall'implementazione di Nullable. Quindi non dovremmo essere bloccati concentrandoci esclusivamente sul tipo Nullable .NET, ma piuttosto incorporare la nostra comprensione dei suoi requisiti e comportamenti nel processo di focalizzazione sul concetto generale e pratico di nullability.