Comment vérifier si un objet donné est nullable, autrement dit comment implémenter la méthode suivante ...
bool IsNullableValueType(object o)
{
...
}
EDIT: Je cherche des types de valeur nullable. Je n'avais pas de types de référence en tête.
//Note: This is just a sample. The code has been simplified
//to fit in a post.
public class BoolContainer
{
bool? myBool = true;
}
var bc = new BoolContainer();
const BindingFlags bindingFlags = BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance
;
object obj;
object o = (object)bc;
foreach (var fieldInfo in o.GetType().GetFields(bindingFlags))
{
obj = (object)fieldInfo.GetValue(o);
}
obj fait maintenant référence à un objet de type bool
(System.Boolean
) avec une valeur égale à true
. Ce que je voulais vraiment, c'était un objet de type Nullable<bool>
Alors maintenant, en tant que solution de rechange, j'ai décidé de vérifier si o est nullable et de créer un wrapper nullable autour de obj.
Il existe deux types de nullable - Nullable<T>
et type de référence.
Jon m'a corrigé qu'il était difficile d'obtenir du texte s'il était mis en boîte, mais vous pouvez utiliser des génériques: - Alors, que diriez-vous ci-dessous. Il s'agit en fait de tester le type T
, mais en utilisant le paramètre obj
uniquement pour l'inférence de type générique (afin de faciliter l'appel), cela fonctionnerait presque à l'identique sans le paramètre obj
.
static bool IsNullable<T>(T obj)
{
if (obj == null) return true; // obvious
Type type = typeof(T);
if (!type.IsValueType) return true; // ref-type
if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable<T>
return false; // value-type
}
Mais cela ne fonctionnera pas si bien si vous avez déjà encadré la valeur à une variable d'objet.
Il existe une solution très simple utilisant des surcharges de méthodes
http://deanchalk.com/is-it-nullable/
extrait:
public static class ValueTypeHelper
{
public static bool IsNullable<T>(T t) { return false; }
public static bool IsNullable<T>(T? t) where T : struct { return true; }
}
puis
static void Main(string[] args)
{
int a = 123;
int? b = null;
object c = new object();
object d = null;
int? e = 456;
var f = (int?)789;
bool result1 = ValueTypeHelper.IsNullable(a); // false
bool result2 = ValueTypeHelper.IsNullable(b); // true
bool result3 = ValueTypeHelper.IsNullable(c); // false
bool result4 = ValueTypeHelper.IsNullable(d); // false
bool result5 = ValueTypeHelper.IsNullable(e); // true
bool result6 = ValueTypeHelper.IsNullable(f); // true
La question de "Comment vérifier si un type est nullable?" est en fait "Comment vérifier si un type est Nullable<>
?", ce qui peut être généralisé à "Comment vérifier si un type est un type construit de type générique?", afin qu'il ne réponde pas seulement à la question "Est-ce que Nullable<int>
est-il un Nullable<>
? ", mais aussi" Est-ce que List<int>
est un List<>
? ".
La plupart des solutions fournies utilisent la méthode Nullable.GetUnderlyingType()
, qui ne fonctionnera évidemment qu'avec le cas de Nullable<>
. Je ne voyais pas la solution de réflexion générale qui fonctionnerait avec un type générique, j'ai donc décidé de l'ajouter ici à la postérité, même si cette question a déjà été répondue il y a longtemps.
Pour vérifier si un type est une forme de Nullable<>
utilisant la réflexion, vous devez d'abord convertir votre type générique construit, par exemple Nullable<int>
, en définition de type générique, Nullable<>
. Vous pouvez le faire en utilisant la méthode GetGenericTypeDefinition()
de la classe Type
. Vous pouvez ensuite comparer le type résultant à Nullable<>
:
Type typeToTest = typeof(Nullable<int>);
bool isNullable = typeToTest.GetGenericTypeDefinition() == typeof(Nullable<>);
// isNullable == true
La même chose peut être appliquée à n'importe quel type générique:
Type typeToTest = typeof(List<int>);
bool isList = typeToTest.GetGenericTypeDefinition() == typeof(List<>);
// isList == true
Plusieurs types peuvent sembler identiques, mais un nombre différent d'arguments de type signifie qu'il s'agit d'un type complètement différent.
Type typeToTest = typeof(Action<DateTime, float>);
bool isAction1 = typeToTest.GetGenericTypeDefinition() == typeof(Action<>);
bool isAction2 = typeToTest.GetGenericTypeDefinition() == typeof(Action<,>);
bool isAction3 = typeToTest.GetGenericTypeDefinition() == typeof(Action<,,>);
// isAction1 == false
// isAction2 == true
// isAction3 == false
Étant donné que les objets Type
sont instanciés une fois par type, vous pouvez vérifier l’égalité de référence entre eux. Donc, si vous voulez vérifier si deux objets ont la même définition de type générique, vous pouvez écrire:
var listOfInts = new List<int>();
var listOfStrings = new List<string>();
bool areSameGenericType =
listOfInts.GetType().GetGenericTypeDefinition() ==
listOfStrings.GetType().GetGenericTypeDefinition();
// areSameGenericType == true
Si vous souhaitez vérifier si un objet est nullable, plutôt que Type
, vous pouvez utiliser la technique ci-dessus avec la solution de Marc Gravell pour créer une méthode assez simple:
static bool IsNullable<T>(T obj)
{
if (!typeof(T).IsGenericType)
return false;
return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
}
Eh bien, vous pourriez utiliser:
return !(o is ValueType);
... mais un objet lui-même n'est pas nullable ou autrement - un type est. Comment aviez-vous prévu de l'utiliser?
Cela fonctionne pour moi et semble simple:
static bool IsNullable<T>(T obj)
{
return default(T) == null;
}
Le moyen le plus simple que je puisse comprendre est:
public bool IsNullable(object obj)
{
Type t = obj.GetType();
return t.IsGenericType
&& t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
Il y a deux problèmes ici: 1) tester pour voir si un Type est nullable; et 2) tester pour voir si un objet représente un type nullable.
Pour le problème 1 (tester un type), voici une solution que j'ai utilisée dans mes propres systèmes: TypeIsNullable-check solution
Pour le problème 2 (test d'un objet), la solution de Dean Chalk ci-dessus fonctionne pour les types de valeur, mais pas pour les types de référence, car l'utilisation de la surcharge <T> renvoie toujours la valeur false. Comme les types de référence sont intrinsèquement nuls, le test d’un type de référence doit toujours renvoyer true. Veuillez vous reporter à la note [à propos de "nullability"] ci-dessous pour une explication de cette sémantique. Voici donc ma modification de l'approche de 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;
}
Et voici ma modification du code client-test pour la solution ci-dessus:
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
La raison pour laquelle j'ai modifié l'approche de Dean dans IsObjectNullable <T> (T t) est que son approche d'origine renvoyait toujours la valeur false pour un type de référence. Dans la mesure où une méthode telle que IsObjectNullable devrait être capable de gérer les valeurs de type référence et puisque tous les types référence sont par nature nullables, alors si un type référence ou une valeur null est passé, la méthode doit toujours renvoyer true.
Les deux méthodes ci-dessus peuvent être remplacées par la méthode unique suivante et obtenir le même résultat:
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<>);
}
Cependant, le problème avec cette dernière approche à méthode unique est que les performances en pâtissent lorsqu'un paramètre Nullable <T> est utilisé. L'exécution de la dernière ligne de cette méthode nécessite beaucoup plus de temps de processeur que pour permettre au compilateur de choisir la deuxième surcharge de méthode indiquée précédemment lorsqu'un paramètre de type Nullable <T> est utilisé dans l'appel IsObjectNullable. Par conséquent, la solution optimale consiste à utiliser l'approche à deux méthodes illustrée ici.
CAVEAT: cette méthode ne fonctionne de manière fiable que si elle est appelée à l'aide de la référence d'objet d'origine ou d'une copie exacte, comme indiqué dans les exemples. Toutefois, si un objet nullable est mis en boîte avec un autre type (tel qu'un objet, etc.) au lieu de rester dans sa forme Nullable <> d'origine, cette méthode ne fonctionnera pas de manière fiable. Si le code appelant cette méthode n'utilise pas la référence d'objet originale, sans boîte ni une copie exacte, il ne peut pas déterminer de manière fiable la nullité de l'objet à l'aide de cette méthode.
Dans la plupart des scénarios de codage, pour déterminer la nullité, il faut au lieu de cela tester le type de l'objet d'origine, et non sa référence (par exemple, le code doit avoir accès au type d'origine de l'objet pour déterminer la nullité). Dans ces cas plus courants, IsTypeNullable (voir lien) est une méthode fiable pour déterminer la nullité.
P.S. - A propos de "nullability"
Je devrais répéter une déclaration à propos de la nullité que j'ai faite dans un article séparé, qui s'applique directement à traiter correctement de ce sujet. C’est-à-dire que j’estime que l’objet de la discussion ici ne devrait pas être de savoir comment vérifier si un objet est un type Nullable générique, mais plutôt si l’on peut affecter une valeur null à un objet de ce type. En d'autres termes, je pense que nous devrions déterminer si un type d'objet est nullable, et non s'il est Nullable. La différence réside dans la sémantique, à savoir les raisons pratiques pour déterminer l'annulation, qui est généralement tout ce qui compte.
Dans un système utilisant des objets de types éventuellement inconnus jusqu'au moment de l'exécution (services Web, appels distants, bases de données, flux, etc.), il est généralement nécessaire de déterminer si une valeur null peut être affectée à l'objet ou si l'objet peut contenir un nul. L'exécution de telles opérations sur des types non nullables générera probablement des erreurs, généralement des exceptions, qui sont très onéreuses en termes de performances et d'exigences de codage. Pour adopter l'approche hautement privilégiée consistant à éviter de manière proactive de tels problèmes, il est nécessaire de déterminer si un objet d'un type arbitraire est capable de contenir un null; c'est-à-dire, s'il est généralement 'nullable'.
Dans un sens très pratique et typique, l'annulation de la nullité en termes .NET n'implique pas du tout que le type d'un objet est une forme de Nullable. Dans de nombreux cas, les objets ont des types de référence, peuvent contenir une valeur null et sont donc tous nullables; aucun d'entre eux n'a un type Nullable. Par conséquent, pour des raisons pratiques dans la plupart des scénarios, il convient de tester le concept général de nullabilité, par opposition au concept de Nullable dépendant de la mise en œuvre. Nous ne devrions donc pas nous arrêter en nous concentrant uniquement sur le type .NET Nullable, mais plutôt intégrer notre compréhension de ses exigences et de son comportement dans le processus de définition du concept général et pratique de nullabilité.
Attention, en boxant un type nullable (Nullable<int>
ou int? Par exemple):
int? nullValue = null;
object boxedNullValue = (object)nullValue;
Debug.Assert(boxedNullValue == null);
int? value = 10;
object boxedValue = (object)value;
Debug.Assert( boxedValue.GetType() == typeof(int))
Cela devient un vrai type de référence, vous perdez donc le fait qu'il était nullable.
La solution la plus simple que j'ai proposée consiste à implémenter la solution de Microsoft ( Comment: identifier un type nullable (Guide de programmation C #) ) en tant que méthode d'extension:
public static bool IsNullable(this Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() != typeof(Nullable<>);
}
Cela peut alors s'appeler comme suit:
bool isNullable = typeof(int).IsNullable();
Cela semble également un moyen logique d’accéder à IsNullable()
car il s’intègre à toutes les autres méthodes IsXxxx()
de la classe Type
.
Peut-être un peu hors sujet, mais toujours des informations intéressantes. Je trouve beaucoup de personnes qui utilisent Nullable.GetUnderlyingType() != null
pour identifier si un type est nullable. Cela fonctionne évidemment, mais Microsoft conseille le type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)
(voir http://msdn.Microsoft.com/en-us/library/ms366789.aspx ).
J'ai regardé cela du point de vue de la performance. La conclusion du test (un million de tentatives) ci-dessous est que lorsqu'un type est nullable, l'option Microsoft offre les meilleures performances.
Nullable.GetUnderlyingType ():1335ms (3 fois plus lent)
(GetGenericTypeDefinition () == typeof (Nullable <>):} _ 500ms
Je sais que nous parlons d'un peu de temps, mais tout le monde aime Tweak les millisecondes :-)! Donc, si votre patron veut que vous réduisiez quelques millisecondes, ceci est votre sauveur ...
/// <summary>Method for testing the performance of several options to determine if a type is nullable</summary>
[TestMethod]
public void IdentityNullablePerformanceTest()
{
int attempts = 1000000;
Type nullableType = typeof(Nullable<int>);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int attemptIndex = 0; attemptIndex < attempts; attemptIndex++)
{
Assert.IsTrue(Nullable.GetUnderlyingType(nullableType) != null, "Expected to be a nullable");
}
Console.WriteLine("Nullable.GetUnderlyingType(): {0} ms", stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
for (int attemptIndex = 0; attemptIndex < attempts; attemptIndex++)
{
Assert.IsTrue(nullableType.IsGenericType && nullableType.GetGenericTypeDefinition() == typeof(Nullable<>), "Expected to be a nullable");
}
Console.WriteLine("GetGenericTypeDefinition() == typeof(Nullable<>): {0} ms", stopwatch.ElapsedMilliseconds);
stopwatch.Stop();
}
Je pense que ceux qui utilisent les tests suggérés par Microsoft contre IsGenericType
sont bons, mais dans le code pour GetUnderlyingType
, Microsoft utilise un test supplémentaire pour vérifier que vous n'avez pas passé la définition de type générique Nullable<>
:
public static bool IsNullableType(this Type nullableType) =>
// instantiated generic type only
nullableType.IsGenericType &&
!nullableType.IsGenericTypeDefinition &&
Object.ReferenceEquals(nullableType.GetGenericTypeDefinition(), typeof(Nullable<>));
Cette version:
:
public static class IsNullable<T>
{
private static readonly Type type = typeof(T);
private static readonly bool is_nullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
public static bool Result { get { return is_nullable; } }
}
bool is_nullable = IsNullable<int?>.Result;