Как определить, имеет ли объект времени выполнения обнуляемый тип
Во-первых: это не дубликат Как проверить, является ли объект обнуляемым?, Или, по крайней мере, на этот вопрос не было дано никакого полезного ответа, и дальнейшая разработка автора фактически задала вопрос, как определить, является ли данный тип (например, возвращаемый из MethodInfo.ReturnType) обнуляемым.
Однако это легко. Сложно определить, относится ли объект времени выполнения, тип которого неизвестен во время компиляции, к типу NULL. Рассматривать:
public void Foo(object obj)
{
var bar = IsNullable(obj);
}
private bool IsNullable(object obj)
{
var type = obj.GetType();
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
// Alternatively something like:
// return !(Nullable.GetUnderlyingType(type) != null);
}
Это не работает, как задумано, потому что GetType()
вызов приведет к операции упаковки ( https://msdn.microsoft.com/en-us/library/ms366789.aspx) и вернет базовый тип значения, а не обнуляемый тип. таким образом IsNullable()
всегда будет возвращать false.
Теперь следующий трюк использует вывод параметра типа, чтобы получить тип (unboxed):
private bool IsNullable<T>(T obj)
{
var type = typeof(T);
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
Это кажется изначально многообещающим. Однако вывод параметров типа работает только тогда, когда тип объекта известен во время компиляции. Так:
public void Foo(object obj)
{
int? genericObj = 23;
var bar1 = IsNullable(genericObj); // Works
var bar2 = IsNullable(obj); // Doesn't work (type parameter is Object)
}
В заключение: общая проблема не в том, чтобы определить, является ли тип обнуляемым, а в том, чтобы сначала получить его.
Итак, моя задача заключается в следующем: как определить, может ли объект времени выполнения (параметр obj в приведенном выше примере) обнуляться? Снеси меня:)
1 ответ
Ну, ты опоздал. Значение в штучной упаковке больше не имеет информацию о типе, который вы ищете - бокс int?
Значение результатов либо в null
или в штучной упаковке int
, В некотором смысле это аналог вызова GetType
на null
- это не имеет смысла, нет информации о типе.
Если вы можете, придерживайтесь общих методов вместо бокса как можно больше (dynamic
может быть очень полезным для некоторых интерфейсов между фактическими значениями object
с). Если вы не можете, вам придется использовать свой собственный "бокс" - или даже просто создать свой собственный Nullable
типа, который будет class
а не очень хакерский struct
вещь:D
При необходимости, вы можете даже сделать Nullable
наберите "А struct
, Это не struct
-нарушает информацию о типе - не сам бокс уничтожает эту информацию, а хаки CLR, которые позволяют Nullable
чтобы соответствовать производительности ненулевых значений. Это очень умный и очень полезный инструмент, но он ломает некоторые хаки, основанные на отражениях (например, тот, который вы пытаетесь сделать).
Это работает как ожидалось:
struct MyNullable<T>
{
private bool hasValue;
private T value;
public static MyNullable<T> FromValue(T value)
{
return new MyNullable<T>() { hasValue = true, value = value };
}
public static implicit operator T (MyNullable<T> n)
{
return n.value;
}
}
private bool IsMyNullable(object obj)
{
if (obj == null) return true; // Duh
var type = obj.GetType().Dump();
return type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(MyNullable<>);
}
Делать то же самое с System.Nullable
не делает; даже просто делаю new int?(42).GetType()
дает тебе System.Int32
вместо System.Nullable<System.Int32>
,
System.Nullable
не настоящий тип - он получает специальную обработку во время выполнения. Он делает вещи, которые вы просто не можете реплицировать с вашими собственными типами, потому что хак даже не в определении типа в IL - он прямо в самом CLR. Еще одна приятная утечка в абстракции состоит в том, что обнуляемый тип не считается struct
- если ваше ограничение общего типа struct
, вы не можете использовать обнуляемый тип. Зачем? Что ж, добавление этого ограничения означает, что использовать new T?()
- что было бы невозможно в противном случае, так как вы не можете сделать обнуляемым обнуляемым. Это легко с MyNullable
типа я написал здесь, но не с System.Nullable
,
РЕДАКТИРОВАТЬ:
Соответствующая часть спецификации CLI (1.8.2.4 - Упаковка и распаковка значения):
У всех типов значений есть операция с именем box. Упаковка значения любого типа значения производит его упакованное значение; т. е. значение соответствующего коробочного типа, содержащее побитовую копию исходного значения. Если тип значения имеет тип NULL, определяемый как экземпляр типа значения System.Nullable, то результатом является нулевая ссылка или побитовая копия его свойства Value типа T, в зависимости от его свойства HasValue (соответственно false и true), У всех коробочных типов есть операция unbox, которая приводит к управляемому указателю на битовое представление значения.
Итак, по определению, box
Операция над типами, допускающими значение NULL, создает либо нулевую ссылку, либо сохраненное значение, а не "упакованный в Nullable".