¿Cómo verificar si un objeto es anulable?

¿Cómo puedo verificar si un objeto determinado puede contener nulos? En otras palabras, cómo implementar el siguiente método …

bool IsNullableValueType(object o) { ... } 

EDITAR: Estoy buscando tipos de valores que aceptan valores. No tenía los tipos de ref en mente.

 //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 ahora se refiere a un objeto de tipo bool ( System.Boolean ) con valor igual a true . Lo que realmente quería era un objeto de tipo Nullable

Así que ahora, como un trabajo en conjunto, decidí verificar si o es anulable y crear un contenedor de nullable alrededor de obj.

Hay dos tipos de Nullable : Nullable y tipo de referencia.

Jon me ha corregido que es difícil obtener el tipo si está en caja, pero se puede con generics: – entonces, ¿qué hay de debajo? Esto es en realidad probar el tipo T , pero usando el parámetro obj puramente para la inferencia de tipo genérico (para hacer que sea fácil llamar), sin embargo, funcionaría casi de manera idéntica sin el obj param.

 static bool IsNullable(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 return false; // value-type } 

Pero esto no funcionará tan bien si ya ha encajonado el valor de una variable de objeto.

Hay una solución muy simple que usa sobrecargas de métodos

http://deanchalk.com/is-it-nullable/

extracto:

 public static class ValueTypeHelper { public static bool IsNullable(T t) { return false; } public static bool IsNullable(T? t) where T : struct { return true; } } 

entonces

 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 pregunta “¿Cómo verificar si un tipo es anulable?” es en realidad “¿Cómo verificar si un tipo es Nullable<> ?”, que se puede generalizar a “¿Cómo verificar si un tipo es un tipo construido de algún tipo genérico?”, para que no solo responda a la pregunta “¿Es Nullable a Nullable<> ? “, pero también” Is List a List<> ? “.

La mayor parte de la solución provista usa el método Nullable.GetUnderlyingType() , que obviamente solo funcionará con el caso de Nullable<> . No vi la solución reflexiva general que funcionará con ningún tipo genérico, así que decidí agregarla aquí para la posteridad, a pesar de que esta pregunta ya fue respondida hace mucho tiempo.

Para verificar si un tipo es alguna forma de Nullable<> usando reflexión, primero debe convertir su tipo genérico construido, por ejemplo Nullable , en la definición de tipo genérico, Nullable<> . Puede hacerlo utilizando el método GetGenericTypeDefinition() de la clase Type . A continuación, puede comparar el tipo resultante con Nullable<> :

 Type typeToTest = typeof(Nullable); bool isNullable = typeToTest.GetGenericTypeDefinition() == typeof(Nullable<>); // isNullable == true 

Lo mismo se puede aplicar a cualquier tipo genérico:

 Type typeToTest = typeof(List); bool isList = typeToTest.GetGenericTypeDefinition() == typeof(List<>); // isList == true 

Varios tipos pueden parecer lo mismo, pero una cantidad diferente de argumentos tipo significa que es un tipo completamente diferente.

 Type typeToTest = typeof(Action); bool isAction1 = typeToTest.GetGenericTypeDefinition() == typeof(Action<>); bool isAction2 = typeToTest.GetGenericTypeDefinition() == typeof(Action<,>); bool isAction3 = typeToTest.GetGenericTypeDefinition() == typeof(Action<,,>); // isAction1 == false // isAction2 == true // isAction3 == false 

Dado que el objeto Type se crea una instancia por tipo, puede verificar la igualdad de referencia entre ellos. Entonces, si desea verificar si dos objetos tienen la misma definición de tipo genérico, puede escribir:

 var listOfInts = new List(); var listOfStrings = new List(); bool areSameGenericType = listOfInts.GetType().GetGenericTypeDefinition() == listOfStrings.GetType().GetGenericTypeDefinition(); // areSameGenericType == true 

Si desea comprobar si un objeto es nulo, en lugar de un Type , puede utilizar la técnica anterior junto con la solución de Marc Gravell para crear un método bastante simple:

 static bool IsNullable(T obj) { if (!typeof(T).IsGenericType) return false; return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>); } 

Bueno, podrías usar:

 return !(o is ValueType); 

… pero un objeto en sí mismo no puede contener nulos o de lo contrario, un tipo es. ¿Cómo planeabas usar esto?

Esto funciona para mí y parece simple:

 static bool IsNullable(T obj) { return default(T) == null; } 

La forma más simple que puedo descubrir es:

 public bool IsNullable(object obj) { Type t = obj.GetType(); return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>); } 

Aquí hay dos problemas: 1) probar para ver si un Tipo es nulable; y 2) pruebas para ver si un objeto representa un Tipo que admite nulos.

Para el número 1 (probar un Tipo), aquí hay una solución que he usado en mis propios sistemas: Solución de comprobación de TypeIsNullable

Para el problema 2 (prueba de un objeto), la solución anterior de Dean Chalk funciona para los tipos de valor, pero no funciona para los tipos de referencia, ya que el uso de la sobrecarga siempre devuelve falso. Como los tipos de referencia son intrínsecamente anulables, probar un tipo de referencia siempre debería ser verdadero. Consulte la nota [Acerca de “Nulability”] a continuación para obtener una explicación de estas semánticas. Por lo tanto, aquí está mi modificación al enfoque de Dean:

  public static bool IsObjectNullable(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? obj) where T : struct { // Always return true, since the object-type passed is guaranteed by the compiler to always be nullable return true; } 

Y aquí está mi modificación al código de prueba del cliente para la solución anterior:

  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 razón por la que he modificado el enfoque de Dean en IsObjectNullable (T t) es que su enfoque original siempre devuelve falso para un tipo de referencia. Dado que un método como IsObjectNullable debería ser capaz de manejar valores de tipo de referencia y dado que todos los tipos de referencia son intrínsecamente nulables, entonces si se pasa un tipo de referencia o un nulo, el método siempre debe devolver verdadero.

Los dos métodos anteriores podrían reemplazarse con el siguiente método único y lograr el mismo resultado:

  public static bool IsObjectNullable(T obj) { Type argType = typeof(T); if (!argType.IsValueType || obj == null) return true; return argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(Nullable<>); } 

Sin embargo, el problema con este último enfoque de método único es que el rendimiento sufre cuando se utiliza un parámetro Nullable . Se necesita mucho más tiempo de procesador para ejecutar la última línea de este único método que para permitir que el comstackdor elija la segunda sobrecarga de método mostrada previamente cuando se usa un parámetro de tipo de Nullable en la llamada IsObjectNullable. Por lo tanto, la solución óptima es utilizar el enfoque de dos métodos que se ilustra aquí.

CAVEAT: este método funciona confiablemente solo si se llama utilizando la referencia de objeto original o una copia exacta, como se muestra en los ejemplos. Sin embargo, si un objeto que admite nulos se encajona en otro Tipo (como objeto, etc.) en lugar de permanecer en su forma Nullable <> original, este método no funcionará de manera confiable. Si el código que llama a este método no está utilizando la referencia original del objeto sin caja o una copia exacta, no puede determinar de manera confiable la capacidad de anulación del objeto utilizando este método.

En la mayoría de los escenarios de encoding, para determinar la nulidad uno debe confiar en probar el Tipo del objeto original, no su referencia (por ejemplo, el código debe tener acceso al Tipo original del objeto para determinar la capacidad de anulación). En estos casos más comunes, IsTypeNullable (ver enlace) es un método confiable para determinar la nulabilidad.

PS – Acerca de “Nulability”

Debo repetir una statement sobre anulabilidad que hice en una publicación separada, que se aplica directamente para abordar adecuadamente este tema. Es decir, creo que el foco de la discusión aquí no debería ser cómo verificar si un objeto es un tipo anulable genérico, sino más bien si se puede asignar un valor de nulo a un objeto de su tipo. En otras palabras, creo que deberíamos determinar si un tipo de objeto es nulo, no si es Nullable. La diferencia radica en la semántica, es decir, en las razones prácticas para determinar la nulidad, que generalmente es lo único que importa.

En un sistema que usa objetos con tipos posiblemente desconocidos hasta el tiempo de ejecución (servicios web, llamadas remotas, bases de datos, fonts, etc.), un requisito común es determinar si se puede asignar un valor nulo al objeto o si el objeto puede contener un nulo. La realización de tales operaciones en tipos que no admiten nulos probablemente producirá errores, generalmente excepciones, que son muy costosas tanto en términos de rendimiento como de requisitos de encoding. Para adoptar el enfoque altamente preferido de evitar dichos problemas de forma proactiva, es necesario determinar si un objeto de un Tipo arbitrario es capaz de contener un nulo; es decir, si generalmente es ‘nullable’.

En un sentido muy práctico y típico, la nulabilidad en términos de .NET no implica necesariamente que el Tipo de un objeto sea una forma de Nullable. En muchos casos, de hecho, los objetos tienen tipos de referencia, pueden contener un valor nulo y, por lo tanto, son anulables; ninguno de estos tiene un tipo Nullable. Por lo tanto, para fines prácticos en la mayoría de los escenarios, se deben realizar pruebas para el concepto general de nulability, frente al concepto de Nullable dependiente de la implementación. Por lo tanto, no deberíamos colgarnos centrándonos únicamente en el tipo .NET Nullable, sino más bien incorporar nuestra comprensión de sus requisitos y comportamiento en el proceso de centrarse en el concepto general y práctico de nulability.

Tenga cuidado, al boxear un tipo anulable ( Nullable o int? Por ejemplo):

 int? nullValue = null; object boxedNullValue = (object)nullValue; Debug.Assert(boxedNullValue == null); int? value = 10; object boxedValue = (object)value; Debug.Assert( boxedValue.GetType() == typeof(int)) 

Se convierte en un verdadero tipo de referencia, por lo que se pierde el hecho de que era nulo.

La solución más simple que se me ocurrió es implementar la solución de Microsoft ( Cómo: Identificar un tipo anulable (Guía de progtwigción de C #) ) como método de extensión:

 public static bool IsNullable(this Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() != typeof(Nullable<>); } 

Esto se puede llamar así:

 bool isNullable = typeof(int).IsNullable(); 

Esto también parece una forma lógica de acceder a IsNullable() porque encaja con todos los otros métodos IsXxxx() de la clase Type .

Tal vez un poco fuera del tema, pero aún hay algo de información interesante. Encuentro mucha gente que usa Nullable.GetUnderlyingType() != null para identificar si un tipo es anulable. Obviamente, esto funciona, pero Microsoft aconseja el siguiente tipo: type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) (ver http://msdn.microsoft.com/en-us/library/ms366789.aspx ).

Miré esto desde el punto de vista del rendimiento. La conclusión de la prueba (un millón de bashs) a continuación es que cuando un tipo es anulable, la opción de Microsoft ofrece el mejor rendimiento.

Nullable.GetUnderlyingType (): 1335ms (3 veces más lento)

GetGenericTypeDefinition () == typeof (Nullable <>): 500ms

Sé que estamos hablando de una pequeña cantidad de tiempo, pero a todo el mundo le encanta modificar los milisegundos :-)! Entonces, si su jefe quiere que reduzca algunos milisegundos, entonces este es su salvador …

 /// Method for testing the performance of several options to determine if a type is nullable [TestMethod] public void IdentityNullablePerformanceTest() { int attempts = 1000000; Type nullableType = typeof(Nullable); 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(); } 

Esta versión:

  • los resultados del almacenamiento en caché son más rápidos,
  • no requiere variables innecesarias, como Method (T obj)
  • NO COMPLICADO :),
  • solo clase genérica estática, que tiene campos computados de una sola vez

:

 public static class IsNullable { 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.Result; 

Esto es lo que se me ocurrió, ya que todo lo demás parecía fallar, al menos en el PLC – Portable Class Library / .NET Core con> = C # 6

Solución: amplíe los métodos estáticos para cualquier Tipo T y Nullable y use el hecho de que el método de extensión estática, que coincida con el tipo subyacente, se invocará y tendrá prioridad sobre el método de extensión T genérico.

Para T :

 public static partial class ObjectExtension { public static bool IsNullable(this T self) { return false; } } 

y para Nullable

 public static partial class NullableExtension { public static bool IsNullable(this Nullable self) where T : struct { return true; } } 

Usar Reflection y type.IsGenericType … no funcionó en mi conjunto actual de .NET Runtimes. Tampoco ayudó la documentación de MSDN .

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {…}

En parte porque la Reflection API se ha cambiado bastante significativamente en .NET Core.

una forma simple de hacer esto:

  public static bool IsNullable(this Type type) { if (type.IsValueType) return Activator.CreateInstance(type) == null; return true; } 

estas son mis pruebas unitarias y todas pasaron

  IsNullable_String_ShouldReturn_True IsNullable_Boolean_ShouldReturn_False IsNullable_Enum_ShouldReturn_Fasle IsNullable_Nullable_ShouldReturn_True IsNullable_Class_ShouldReturn_True IsNullable_Decimal_ShouldReturn_False IsNullable_Byte_ShouldReturn_False IsNullable_KeyValuePair_ShouldReturn_False 

pruebas unitarias reales

  [TestMethod] public void IsNullable_String_ShouldReturn_True() { var typ = typeof(string); var result = typ.IsNullable(); Assert.IsTrue(result); } [TestMethod] public void IsNullable_Boolean_ShouldReturn_False() { var typ = typeof(bool); var result = typ.IsNullable(); Assert.IsFalse(result); } [TestMethod] public void IsNullable_Enum_ShouldReturn_Fasle() { var typ = typeof(System.GenericUriParserOptions); var result = typ.IsNullable(); Assert.IsFalse(result); } [TestMethod] public void IsNullable_Nullable_ShouldReturn_True() { var typ = typeof(Nullable); var result = typ.IsNullable(); Assert.IsTrue(result); } [TestMethod] public void IsNullable_Class_ShouldReturn_True() { var typ = typeof(TestPerson); var result = typ.IsNullable(); Assert.IsTrue(result); } [TestMethod] public void IsNullable_Decimal_ShouldReturn_False() { var typ = typeof(decimal); var result = typ.IsNullable(); Assert.IsFalse(result); } [TestMethod] public void IsNullable_Byte_ShouldReturn_False() { var typ = typeof(byte); var result = typ.IsNullable(); Assert.IsFalse(result); } [TestMethod] public void IsNullable_KeyValuePair_ShouldReturn_False() { var typ = typeof(KeyValuePair); var result = typ.IsNullable(); Assert.IsFalse(result); }