¿Cómo convierto un System.Type a su versión anulable?

79

Una vez más, uno de esos: "¿Existe una forma integrada más fácil de hacer las cosas en lugar de mi método de ayuda?"

Por lo tanto, es fácil obtener el tipo subyacente de un tipo que acepta valores NULL, pero ¿cómo obtengo la versión que admite valores NULL de un tipo .NET?

Así que tengo

typeof(int)
typeof(DateTime)
System.Type t = something;

y yo quiero

int? 
DateTime?

o

Nullable<int> (which is the same)
if (t is primitive) then Nullable<T> else just T

¿Existe un método incorporado?

Alex Duggleby
fuente

Respuestas:

115

Aquí está el código que uso:

Type GetNullableType(Type type) {
    // Use Nullable.GetUnderlyingType() to remove the Nullable<T> wrapper if type is already nullable.
    type = Nullable.GetUnderlyingType(type) ?? type; // avoid type becoming null
    if (type.IsValueType)
        return typeof(Nullable<>).MakeGenericType(type);
    else
        return type;
}
Alex Lyman
fuente
¡Agradable! Esta es una solución realmente genial.
ljs
¡Gran respuesta! Justo lo que estaba buscando.
Alex Duggleby
10
Tenga en cuenta que Nullable.GetUnderlyingType devuelve nulo si el tipo no admite nulos, por lo que deberá verificarlo.
Amit G
1
@AmitG Tienes razón. Me enfrento a un nullerror cuando paso el tipo de valor a esta función.
Iluminador
1
Realmente necesita asegurarse de que typeno sea nulo antes de acceder a la IsValueTypepropiedad, como dijo @AmitG. -1 hasta que se solucione
Matt Thomas
16

Tengo un par de métodos que he escrito en mi biblioteca de utilidades en los que he confiado mucho. El primero es un método que convierte cualquier tipo a su forma Nullable <Type> correspondiente:

    /// <summary>
    /// [ <c>public static Type GetNullableType(Type TypeToConvert)</c> ]
    /// <para></para>
    /// Convert any Type to its Nullable&lt;T&gt; form, if possible
    /// </summary>
    /// <param name="TypeToConvert">The Type to convert</param>
    /// <returns>
    /// The Nullable&lt;T&gt; converted from the original type, the original type if it was already nullable, or null 
    /// if either <paramref name="TypeToConvert"/> could not be converted or if it was null.
    /// </returns>
    /// <remarks>
    /// To qualify to be converted to a nullable form, <paramref name="TypeToConvert"/> must contain a non-nullable value 
    /// type other than System.Void.  Otherwise, this method will return a null.
    /// </remarks>
    /// <seealso cref="Nullable&lt;T&gt;"/>
    public static Type GetNullableType(Type TypeToConvert)
    {
        // Abort if no type supplied
        if (TypeToConvert == null)
            return null;

        // If the given type is already nullable, just return it
        if (IsTypeNullable(TypeToConvert))
            return TypeToConvert;

        // If the type is a ValueType and is not System.Void, convert it to a Nullable<Type>
        if (TypeToConvert.IsValueType && TypeToConvert != typeof(void))
            return typeof(Nullable<>).MakeGenericType(TypeToConvert);

        // Done - no conversion
        return null;
    }

El segundo método simplemente informa si un tipo determinado es anulable. Este método es llamado por el primero y es útil por separado:

    /// <summary>
    /// [ <c>public static bool IsTypeNullable(Type TypeToTest)</c> ]
    /// <para></para>
    /// Reports whether a given Type is nullable (Nullable&lt; Type &gt;)
    /// </summary>
    /// <param name="TypeToTest">The Type to test</param>
    /// <returns>
    /// true = The given Type is a Nullable&lt; Type &gt;; false = The type is not nullable, or <paramref name="TypeToTest"/> 
    /// is null.
    /// </returns>
    /// <remarks>
    /// This method tests <paramref name="TypeToTest"/> and reports whether it is nullable (i.e. whether it is either a 
    /// reference type or a form of the generic Nullable&lt; T &gt; type).
    /// </remarks>
    /// <seealso cref="GetNullableType"/>
    public static bool IsTypeNullable(Type TypeToTest)
    {
        // Abort if no type supplied
        if (TypeToTest == null)
            return false;

        // If this is not a value type, it is a reference type, so it is automatically nullable
        //  (NOTE: All forms of Nullable<T> are value types)
        if (!TypeToTest.IsValueType)
            return true;

        // Report whether TypeToTest is a form of the Nullable<> type
        return TypeToTest.IsGenericType && TypeToTest.GetGenericTypeDefinition() == typeof(Nullable<>);
    }

La implementación IsTypeNullable anterior funciona como un campeón cada vez, pero es un poco detallada y lenta en su última línea de código. El siguiente cuerpo de código es el mismo que el anterior para IsTypeNullable, excepto que la última línea de código es más simple y rápida:

        // Abort if no type supplied
        if (TypeToTest == null)
            return false;

        // If this is not a value type, it is a reference type, so it is automatically nullable
        //  (NOTE: All forms of Nullable<T> are value types)
        if (!TypeToTest.IsValueType)
            return true;

        // Report whether an underlying Type exists (if it does, TypeToTest is a nullable Type)
        return Nullable.GetUnderlyingType(TypeToTest) != null;

¡Disfrutar!

marca

PD: Acerca de la "nulabilidad"

Debo repetir una declaración sobre la nulabilidad que hice en una publicación separada, que se aplica directamente para abordar correctamente este tema. Es decir, creo que el enfoque de la discusión aquí no debería ser cómo verificar si un objeto es un tipo genérico que acepta valores NULL, sino más bien si se puede asignar un valor nulo a un objeto de su tipo. En otras palabras, creo que deberíamos estar determinando si un tipo de objeto es anulable, no si es anulable. La diferencia está en la semántica, a saber, las razones prácticas para determinar la nulabilidad, que suele ser todo lo que importa.

En un sistema que usa objetos con tipos posiblemente desconocidos hasta el momento de la ejecución (servicios web, llamadas remotas, bases de datos, feeds, etc.), un requisito común es determinar si se puede asignar un nulo al objeto o si el objeto puede contener un nulo. La realización de tales operaciones en tipos que no aceptan valores NULL probablemente producirá errores, generalmente excepciones, que son muy costosos tanto en términos de rendimiento como de requisitos de codificación. Para adoptar el enfoque altamente preferido de evitar proactivamente tales problemas, es necesario determinar si un objeto de un Tipo arbitrario es capaz de contener un nulo; es decir, si es generalmente "anulable".

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. De hecho, en muchos casos, los objetos tienen tipos de referencia, pueden contener un valor nulo y, por lo tanto, son todos anulables; ninguno de estos tiene un tipo que acepta valores NULL. Por lo tanto, para fines prácticos en la mayoría de los escenarios, las pruebas deben realizarse para el concepto general de nulabilidad, frente al concepto dependiente de la implementación de Nullable. Por lo tanto, no deberíamos quedarnos colgados concentrándonos únicamente en el tipo .NET que admite valores nulos, sino que deberíamos incorporar nuestra comprensión de sus requisitos y comportamiento en el proceso de enfocarnos en el concepto general y práctico de nulabilidad.

Mark Jones
fuente
9

La respuesta de Lyman es excelente y me ha ayudado, sin embargo, hay un error más que debe solucionarse.

Nullable.GetUnderlyingType(type)solo debería llamarse si el tipo no es ya un Nullabletipo. De lo contrario, parece devolver erróneamente un valor nulo cuando el tipo se deriva de System.RuntimeType(como cuando paso typeof(System.Int32)). La siguiente versión evita la necesidad de llamar Nullable.GetUnderlyingType(type)comprobando si el tipo es Nullable.

A continuación encontrará una ExtensionMethodversión de este método que inmediatamente devolver el tipo a menos que es una ValueTypeque no está ya Nullable.

Type NullableVersion(this Type sourceType)
{
    if(sourceType == null)
    {
        // Throw System.ArgumentNullException or return null, your preference
    }
    else if(sourceType == typeof(void))
    { // Special Handling - known cases where Exceptions would be thrown
        return null; // There is no Nullable version of void
    }

    return !sourceType.IsValueType
            || (sourceType.IsGenericType
               && sourceType.GetGenericTypeDefinition() == typeof(Nullable<>) )
        ? sourceType
        : typeof(Nullable<>).MakeGenericType(sourceType);
}

(Lo siento, pero no pude simplemente publicar un comentario a la respuesta de Lyman porque era nuevo y aún no tenía suficiente reputación).

Thracx
fuente
2

No hay nada incorporado que yo sepa, ya que int?, etc. es simplemente azúcar sintáctico para Nullable<T>; y no se le da un tratamiento especial más allá de eso. Es especialmente improbable dado que está intentando obtener esto a partir de la información de tipo de un tipo determinado. Por lo general, eso siempre requiere un código de 'roll your own' como un hecho. Tendría que usar Reflection para crear un nuevo Nullabletipo con el parámetro de tipo del tipo de entrada.

Editar: Como sugieren los comentarios, en realidad Nullable<> se trata especialmente, y en el tiempo de ejecución para arrancar como se explica en este artículo .

ljs
fuente
De hecho, estoy bastante seguro de que CLR tiene algo de magia especial para manejar Nullable <> de forma algo diferente. Necesito comprobar esto.
TraumaPony
Estaría interesado en esto, feliz de admitir que estoy equivocado si ese es el caso :-)
ljs
Como puede agregar dos a int?través del +operador, sabemos que Nullablereciben un tratamiento especial porque este tipo de sobrecarga de operadores genéricos no funcionaría de otra manera.
Konrad Rudolph