Как проверить, является ли объект обнуляемым? - PullRequest
186 голосов
/ 17 декабря 2008

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

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

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

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

Ответы [ 14 ]

248 голосов
/ 17 декабря 2008

Существует два типа значений Nullable - 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
}

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

48 голосов
/ 09 ноября 2010

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

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
30 голосов
/ 03 января 2011

Вопрос "Как проверить, может ли тип обнуляться?" на самом деле «Как проверить, является ли тип Nullable<>?», что можно обобщить на «Как проверить, является ли тип составным типом некоторого универсального типа?», так что он не только отвечает на вопрос «Есть Nullable<int> a Nullable<>? ", Но также" Is List<int> a 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<>);
}
22 голосов
/ 01 мая 2014

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

static bool IsNullable<T>(T obj)
{
    return default(T) == null;
}
20 голосов
/ 17 декабря 2008

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

return !(o is ValueType);

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

11 голосов
/ 15 марта 2012

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

public bool IsNullable(object obj)
{
    Type t = obj.GetType();
    return t.IsGenericType 
        && t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
9 голосов
/ 14 октября 2011

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

Для выпуска 1 (тестирование Type) вот решение, которое я использовал в своих собственных системах: Решение 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 (см. Ссылку) является надежным методом определения обнуляемости.

P.S. - про "обнуляемость"

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

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

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

6 голосов
/ 22 декабря 2016

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

public static bool IsNullable(this Type type)
{
    return Nullable.GetUnderlyingType(type) != null;
}

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

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

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

6 голосов
/ 17 декабря 2008

Будьте осторожны, когда помещаете в бокс обнуляемый тип (например, 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))

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

3 голосов
/ 04 июля 2014

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

Я смотрел на это с точки зрения производительности. Заключение теста (один миллион попыток), приведенное ниже, заключается в том, что, когда тип имеет тип NULL, опция 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();
}
...