Как преобразовать System.Type в его версию, допускающую значение NULL?

79

Еще раз один из тех: «Есть ли более простой встроенный способ делать что-то вместо моего вспомогательного метода?»

Так что легко получить базовый тип из типа, допускающего значение NULL, но как мне получить версию типа .NET, допускающую значение NULL?

Так что я

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

и я хочу

int? 
DateTime?

или же

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

Есть ли встроенный метод?

Алекс Дагглби
источник

Ответы:

115

Вот код, который я использую:

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;
}
Алекс Лайман
источник
Ницца! Это действительно отличное решение.
ljs
Отличный ответ! Именно то, что я искал.
Alex Duggleby,
10
Обратите внимание, что Nullable.GetUnderlyingType возвращает null, если тип не допускает значения NULL, поэтому вам необходимо проверить это.
Amit G
1
@AmitG Ты прав. Я сталкиваюсь с nullошибкой, когда передаю тип значения в эту функцию.
Illuminator
1
Как сказал @AmitG, вам действительно нужно убедиться, что typeон не равен нулю, прежде чем обращаться к IsValueTypeсвойству. -1, пока это не будет исправлено
Мэтт Томас
16

У меня есть несколько методов, которые я написал в своей служебной библиотеке, на которые я сильно полагался. Первый - это метод, который преобразует любой тип в соответствующую ему форму 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;
    }

Второй метод просто сообщает, допускает ли данный Тип значение NULL. Этот метод вызывается первым и полезен отдельно:

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

Вышеупомянутая реализация IsTypeNullable каждый раз работает как чемпион, но в последней строке кода она немного многословна и медленна. Следующее тело кода такое же, как и выше для IsTypeNullable, за исключением того, что последняя строка кода проще и быстрее:

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

Наслаждайтесь!

отметка

PS - Насчет "нулевой возможности"

Я должен повторить заявление о допустимости пустых значений, которое я сделал в отдельном сообщении, которое непосредственно относится к правильному рассмотрению этой темы. То есть, я считаю, что основное внимание здесь должно уделяться не тому, как проверить, является ли объект универсальным типом Nullable, а скорее тому, можно ли присвоить значение null объекту этого типа. Другими словами, я думаю, что мы должны определять, допускает ли тип объекта значение NULL, а не его значение. Разница заключается в семантике, а именно в практических причинах определения допустимости значений NULL, что обычно и имеет значение.

В системе, использующей объекты с типами, которые, возможно, неизвестны до времени выполнения (веб-службы, удаленные вызовы, базы данных, каналы и т. Д.), Общим требованием является определение того, может ли объекту быть присвоено значение null или может ли объект содержать нуль. Выполнение таких операций с типами, не допускающими значения NULL, скорее всего, приведет к ошибкам, обычно исключениям, которые очень дороги как с точки зрения производительности, так и требований кодирования. Чтобы воспользоваться наиболее предпочтительным подходом к упреждающему предотвращению таких проблем, необходимо определить, может ли объект произвольного типа содержать значение null; то есть, является ли он вообще «допускающим значение NULL».

В очень практическом и типичном смысле допустимость значений NULL в терминах .NET вовсе не обязательно означает, что Type объекта является формой Nullable. Фактически во многих случаях объекты имеют ссылочные типы, могут содержать нулевое значение и, таким образом, все могут иметь нулевое значение; ни у одного из них нет типа, допускающего значение NULL. Поэтому для практических целей в большинстве сценариев следует проводить тестирование общей концепции допустимости значений NULL, а не концепции Nullable, зависящей от реализации. Таким образом, нам не следует зацикливаться исключительно на типе .NET Nullable, а лучше включить наше понимание его требований и поведения в процесс сосредоточения внимания на общей практической концепции допустимости значений NULL.

Марк Джонс
источник
9

Ответ Лаймана отличный и помог мне, однако есть еще одна ошибка, которую необходимо исправить.

Nullable.GetUnderlyingType(type)следует вызывать только в том случае, если тип еще не является Nullableтипом. В противном случае кажется, что он ошибочно возвращает null, когда тип является производным System.RuntimeType(например, когда я перехожу typeof(System.Int32)). В приведенной ниже версии нет необходимости вызывать Nullable.GetUnderlyingType(type), проверяя, соответствует ли тип Nullable.

Ниже вы найдете ExtensionMethodверсию этого метода, которая немедленно вернет тип, если это еще ValueTypeне сделано 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);
}

(Извините, но я не мог просто опубликовать комментарий к ответу Лаймана, потому что я был новичком и у меня еще не было достаточно репутации.)

Thracx
источник
2

Я не знаю ничего встроенного, поскольку и int?т. Д. - это просто синтаксический сахар Nullable<T>; и не получает особого отношения сверх этого. Это особенно маловероятно, учитывая, что вы пытаетесь получить это из информации о типе данного типа. Обычно это всегда требует некоторого «накрутки собственного» кода как данности. Вам нужно будет использовать Reflection для создания нового Nullableтипа с параметром типа входного типа.

Изменить: Как сказано в комментариях предлагают на самом деле Nullable<> это лечение специально, и во время выполнения для загрузки , как описано в этой статье .

ljs
источник
На самом деле, я почти уверен, что в CLR есть особая магия для обработки Nullable <> несколько иначе. Мне нужно это проверить.
TraumaPony,
Я был бы заинтересован в этом, рад признать, что я ошибаюсь, если это так :-)
ljs
Поскольку вы можете добавить два с int?помощью +оператора, мы знаем, что это Nullableтребует особого обращения, потому что в противном случае такая перегрузка общего оператора не сработала бы.
Конрад Рудольф,