Jak przekonwertować System.Type na jego wersję dopuszczającą wartość null?

79

Jeszcze raz jedno z tych: „Czy istnieje łatwiejszy wbudowany sposób robienia rzeczy zamiast mojej metody pomocniczej?”

Dlatego łatwo jest uzyskać typ bazowy z typu dopuszczającego wartość null, ale jak uzyskać wersję dopuszczającą wartość null dla typu .NET?

Więc mam

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

i chcę

int? 
DateTime?

lub

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

Czy istnieje wbudowana metoda?

Alex Duggleby
źródło

Odpowiedzi:

115

Oto kod, którego używam:

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
źródło
Miły! To naprawdę fajne rozwiązanie.
ljs
Świetna odpowiedź! Właśnie tego szukałem.
Alex Duggleby,
10
Zwróć uwagę, że Nullable.GetUnderlyingType zwraca null, jeśli typ nie dopuszcza wartości null, więc musisz to sprawdzić.
Amit G,
1
@AmitG Masz rację. Napotykam nullbłąd, kiedy przekazuję typ wartości do tej funkcji.
Illuminator
1
Naprawdę musisz upewnić się, że typenie jest null przed uzyskaniem dostępu do IsValueTypewłaściwości, jak powiedział @AmitG. -1, dopóki to nie zostanie naprawione
Matt Thomas
16

Mam kilka metod, które napisałem w mojej bibliotece narzędziowej, na których bardzo polegałem. Pierwsza to metoda, która konwertuje dowolny Type na odpowiadający mu formularz Nullable <Type>:

    /// <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;
    }

Druga metoda po prostu raportuje, czy dany typ dopuszcza wartość null. Ta metoda jest wywoływana przez pierwszą i jest przydatna osobno:

    /// <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<>);
    }

Powyższa implementacja IsTypeNullable działa jak mistrz za każdym razem, ale jest nieco rozwlekła i powolna w ostatniej linii kodu. Następująca treść kodu jest taka sama jak powyżej dla IsTypeNullable, z tą różnicą, że ostatnia linia kodu jest prostsza i szybsza:

        // 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;

Cieszyć się!

znak

PS - Informacje o „nullability”

Powinienem powtórzyć stwierdzenie o nieważności, które zawarłem w osobnym poście, które dotyczy bezpośrednio właściwego zajęcia się tym tematem. Oznacza to, że uważam, że dyskusja tutaj nie powinna skupiać się na tym, jak sprawdzić, czy obiekt jest ogólnym typem Nullable, ale raczej na tym, czy można przypisać wartość null obiektowi tego typu. Innymi słowy, myślę, że powinniśmy określić, czy typ obiektu dopuszcza wartość null, a nie, czy jest dopuszczalny. Różnica dotyczy semantyki, a mianowicie praktycznych powodów określania wartości zerowej, która zwykle jest wszystkim, co się liczy.

W systemie używającym obiektów o typach prawdopodobnie nieznanych do czasu wykonania (usługi sieciowe, zdalne wywołania, bazy danych, źródła danych itp.), Częstym wymaganiem jest określenie, czy obiektowi można przypisać wartość null, czy też obiekt może zawierać null. Wykonywanie takich operacji na typach niepodlegających wartości null prawdopodobnie spowoduje błędy, zwykle wyjątki, które są bardzo kosztowne zarówno pod względem wydajności, jak i wymagań dotyczących kodowania. Aby przyjąć wysoce preferowane podejście do proaktywnego unikania takich problemów, konieczne jest ustalenie, czy obiekt dowolnego typu może zawierać wartość null; tj. czy generalnie dopuszcza się zerową wartość.

W bardzo praktycznym i typowym sensie dopuszczalność wartości null w terminach .NET wcale nie musi oznaczać, że typ obiektu jest formą wartości Nullable. W rzeczywistości w wielu przypadkach obiekty mają typy referencyjne, mogą zawierać wartość null, a zatem wszystkie dopuszczają wartość null; żaden z nich nie ma typu Nullable. Dlatego ze względów praktycznych w większości scenariuszy testowanie powinno być przeprowadzane dla ogólnej koncepcji dopuszczalności wartości zerowej w porównaniu z koncepcją dopuszczalności zerowej zależnej od implementacji. Dlatego nie powinniśmy skupiać się wyłącznie na typie .NET Nullable, ale raczej uwzględnić nasze zrozumienie jego wymagań i zachowania w procesie skupiania się na ogólnej, praktycznej koncepcji zerowalności.

Mark Jones
źródło
9

Odpowiedź Lymana jest świetna i pomogła mi, jednak jest jeszcze jeden błąd, który należy naprawić.

Nullable.GetUnderlyingType(type)powinno nazywać się tylko wtedy, gdy typ nie jest już Nullabletypem. W przeciwnym razie wydaje się, że błędnie zwraca wartość null, gdy typ pochodzi z System.RuntimeType(na przykład gdy przekazuję typeof(System.Int32)). Poniższa wersja pozwala uniknąć konieczności wywoływania Nullable.GetUnderlyingType(type), sprawdzając, czy Nullablezamiast tego typ jest .

Poniżej znajdziesz plik ExtensionMethod wersję tej metody, która natychmiast zwraca typ , chyba że jest to ValueType, że nie jest już 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);
}

(Przepraszam, ale nie mogłem po prostu opublikować komentarza do odpowiedzi Lymana, ponieważ byłem nowy i nie miałem jeszcze wystarczającej liczby powtórzeń).

Thracx
źródło
2

Nie ma nic wbudowanego, o czym wiem, ponieważ int?itp. Jest po prostu cukrem syntaktycznym Nullable<T>; i poza tym nie jest traktowany inaczej. Jest to szczególnie mało prawdopodobne, biorąc pod uwagę, że próbujesz uzyskać to z informacji o typie danego typu. Zwykle zawsze wymaga to „zrolowania własnego” kodu jako danego. Będziesz musiał użyć odbicia, aby utworzyć nowy Nullabletyp z parametrem typu typu wejściowego.

Edycja: Jak sugerują komentarze, w rzeczywistości Nullable<> jest traktowany specjalnie i w środowisku wykonawczym do rozruchu, jak wyjaśniono w tym artykule .

ljs
źródło
Actaully, jestem prawie pewien, że CLR ma jakąś specjalną magię do obsługi Nullable <> w nieco inny sposób. Muszę to sprawdzić.
TraumaPony,
Byłbym tym zainteresowany, z przyjemnością przyznam, że się mylę, jeśli tak jest :-)
ljs
Ponieważ możesz dodać dwa int?za pośrednictwem +operatora, wiemy, że Nullablejest to traktowane w specjalny sposób, ponieważ tego rodzaju ogólne przeciążenie operatora nie zadziałałoby inaczej.
Konrad Rudolph