Как проверить, является ли объект обнуляемым?

Как проверить, является ли данный объект обнуляемым, другими словами, как реализовать следующий метод...

bool IsNullableValueType(object o)
{
    ...
}

РЕДАКТИРОВАТЬ: Я ищу обнуляемые типы значений. Я не имел в виду типы ссылок.

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

объект теперь относится к объекту типа bool (System.Boolean) со значением, равным true, То, что я действительно хотел, было объектом типа Nullable<bool>

Так что теперь, как обходной путь, я решил проверить, может ли o быть обнуляемым, и создать обнуляемую оболочку вокруг obj.

14 ответов

Решение

Есть два типа обнуляемых - Nullable<T> и ссылочный тип.

Джон исправил меня, что трудно получить тип в штучной упаковке, но вы можете с помощью дженериков: - так как насчет ниже. Это на самом деле тип тестирования T, но используя obj Параметр только для логического вывода типа (чтобы его было легко вызвать) - он будет работать почти одинаково без 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
}

Но это не будет работать так хорошо, если вы уже поместили значение в переменную объекта.

Существует очень простое решение, использующее перегрузки методов.

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

выдержка:

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

затем

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

Это работает для меня и кажется простым:

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

Вопрос "Как проверить, можно ли обнулять тип?" на самом деле "Как проверить, является ли тип Nullable<>?", который можно обобщить на" Как проверить, является ли тип составным типом некоторого универсального типа?", чтобы он не только отвечал на вопрос" Является ли Nullable<int> Nullable<>?", но также" Есть List<int> List<>?".

Большая часть предоставленного решения использует Nullable.GetUnderlyingType() метод, который, очевидно, будет работать только в случае Nullable<>, Я не видел общего рефлексивного решения, которое будет работать с любым родовым типом, поэтому я решил добавить его сюда для потомков, хотя на этот вопрос уже давным-давно был дан ответ.

Чтобы проверить, является ли тип какой-то формой Nullable<> используя рефлексию, вы должны сначала преобразовать ваш созданный универсальный тип, например Nullable<int>в определение общего типа, Nullable<>, Вы можете сделать это с помощью GetGenericTypeDefinition() метод Type учебный класс. Затем вы можете сравнить полученный тип с Nullable<>:

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

То же самое можно применить к любому универсальному типу:

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

Несколько типов могут показаться одинаковыми, но разное количество аргументов типа означает, что это совершенно другой тип.

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

поскольку Type Объект создается один раз для каждого типа, вы можете проверить равенство ссылок между ними. Поэтому, если вы хотите проверить, имеют ли два объекта одно и то же определение универсального типа, вы можете написать:

var listOfInts = new List<int>();
var listOfStrings = new List<string>();

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

Если вы хотите проверить, является ли объект обнуляемым, а не Typeзатем вы можете использовать вышеописанную технику вместе с решением Марка Гравелла, чтобы создать довольно простой метод:

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

    return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
}

Ну, вы можете использовать:

return !(o is ValueType);

... но сам объект не может быть обнуляемым или нет - тип есть. Как вы планировали использовать это?

Самое простое решение, которое я придумал, - это реализовать решение Microsoft ( Как: определить тип Nullable (Руководство по программированию в C#)) в качестве метода расширения:

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

Это можно тогда назвать так:

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

Это также кажется логичным способом доступа IsNullable() потому что это вписывается со всеми другими IsXxxx() методы Type учебный класс.

Самый простой способ понять это:

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

Здесь есть две проблемы: 1) тестирование, чтобы увидеть, является ли Тип обнуляемым; и 2) тестирование, чтобы увидеть, представляет ли объект обнуляемый тип.

Для выпуска 1 (тестирование типа) вот решение, которое я использовал в своих собственных системах: решение TypeIsNullable-check

Для проблемы 2 (тестирование объекта) решение Дина Чака, приведенное выше, работает для типов значений, но не работает для ссылочных типов, поскольку перегрузка всегда возвращает false. Поскольку ссылочные типы по своей природе обнуляются, тестирование ссылочного типа всегда должно возвращать true. Пожалуйста, смотрите примечание [О "обнуляемости"] ниже для объяснения этой семантики. Итак, вот моя модификация подхода Дина:

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

А вот моя модификация кода клиентского теста для вышеуказанного решения:

    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

Причина, по которой я изменил подход Дина в IsObjectNullable(T t), заключается в том, что его оригинальный подход всегда возвращал false для ссылочного типа. Поскольку такой метод, как IsObjectNullable, должен иметь возможность обрабатывать значения ссылочного типа и поскольку все ссылочные типы по своей природе могут иметь значение null, то, если передается либо ссылочный тип, либо значение null, метод всегда должен возвращать true.

Вышеупомянутые два метода могут быть заменены следующим единственным методом и получить тот же результат:

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

Однако проблема этого последнего подхода, состоящего из одного метода, заключается в том, что производительность снижается при использовании параметра Nullable. Для выполнения последней строки этого единственного метода требуется гораздо больше процессорного времени, чем для того, чтобы компилятор мог выбрать перегрузку второго метода, показанную ранее, когда параметр Nullable-типа используется в вызове IsObjectNullable. Поэтому оптимальным решением является использование двухметодного подхода, показанного здесь.

CAVEAT: этот метод работает надежно, только если вызывается с использованием исходной ссылки на объект или точной копии, как показано в примерах. Однако, если обнуляемый объект помещается в другой тип (например, объект и т. Д.) Вместо того, чтобы оставаться в исходной форме Nullable<>, этот метод не будет работать надежно. Если код, вызывающий этот метод, не использует исходную, распакованную ссылку на объект или точную копию, он не может надежно определить обнуляемость объекта с помощью этого метода.

В большинстве сценариев кодирования для определения обнуляемости вместо этого нужно полагаться на тестирование Типа исходного объекта, а не его ссылки (например, код должен иметь доступ к исходному Типу объекта для определения обнуляемости). В этих более распространенных случаях IsTypeNullable (см. Ссылку) является надежным методом определения обнуляемости.

PS - про "обнуляемость"

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

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

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

Будьте осторожны, когда бокс обнуляемого типа (Nullable<int> или int? например):

int? nullValue = null;
object boxedNullValue = (object)nullValue;
Debug.Assert(boxedNullValue == null);

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

Он становится истинным ссылочным типом, поэтому вы теряете тот факт, что он был обнуляемым.

Может быть, немного не по теме, но все же некоторая интересная информация. Я нахожу много людей, которые используют Nullable.GetUnderlyingType() != null к идентичности, если тип обнуляется. Это, очевидно, работает, но Microsoft советует следующее type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) (см. http://msdn.microsoft.com/en-us/library/ms366789.aspx).

Я посмотрел на это с точки зрения производительности. Заключение теста (один миллион попыток), приведенное ниже, заключается в том, что, когда тип является обнуляемым, опция Microsoft обеспечивает наилучшую производительность.

Nullable.GetUnderlyingType (): 1335 мс (в 3 раза медленнее)

GetGenericTypeDefinition () == typeof (Nullable<>): 500 мс

Я знаю, что мы говорим о небольшом количестве времени, но все любят подстраивать миллисекунды:-)! Так что, если ваш начальник хочет, чтобы вы сократили несколько миллисекунд, тогда это ваш спаситель...

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

Я думаю, что те, которые используют тестирование Microsoft против IsGenericType хороши, но в коде для GetUnderlyingTypeMicrosoft использует дополнительный тест, чтобы убедиться, что вы не прошли определение общего типа Nullable<>:

 public static bool IsNullableType(this Type nullableType) =>
    // instantiated generic type only                
    nullableType.IsGenericType &&
    !nullableType.IsGenericTypeDefinition &&
    Object.ReferenceEquals(nullableType.GetGenericTypeDefinition(), typeof(Nullable<>));

Вот то, что я придумал, так как все остальное казалось неудачным - по крайней мере, в ПЛК - Portable Class Library / .NET Core с>= C# 6

Решение: расширить статические методы для любого типа T а также Nullable<T> и использовать тот факт, что метод статического расширения, соответствующий базовому типу, будет вызван и будет иметь приоритет над общим T Расширение-метод.

За T:

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

и для Nullable<T>

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

Используя отражение и type.IsGenericType... не работал на моем текущем наборе.NET Runtime. Также не помогла документация MSDN.

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

Частично потому, что API-интерфейс Reflection значительно изменился в.NET Core.

Эта версия:

  • кеширование результатов происходит быстрее,
  • не требует ненужных переменных, таких как Method(T obj)
  • НЕ СЛОЖНО:),
  • просто статический универсальный класс, который имеет одноразовые поля

:

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;

Простой способ сделать это:

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

        return true;
    }

это мои юнит-тесты и все пройдено

    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

фактические юнит-тесты

    [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<bool>);
        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<string, string>);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }
Другие вопросы по тегам