[英]How to determine if a runtime object is of a nullable value type
第一:这不是如何检查对象是否可以为空? . 或者,至少,该问题没有提供有用的答案,作者的进一步阐述实际上询问了如何确定给定类型(例如,从 MethodInfo.ReturnType 返回)是否可为空。
然而,这很容易。 难点在于确定在编译时类型未知的运行时对象是否为可空类型。 考虑:
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。
现在,以下技巧使用类型参数推断来获取(未装箱)类型:
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 参数)是否可以为空? 吹我走:)
好吧,你太晚了。 装箱的值不再具有您寻求的类型信息 - 装箱一个int?
value 导致null
或装箱的int
。 在某种程度上,它类似于在null
上调用GetType
- 它没有意义,没有类型信息。
如果可以的话,尽可能坚持使用泛型方法而不是装箱( dynamic
对于实际可空值和object
之间的某些接口非常有帮助)。 如果你不能,你将不得不使用你自己的“拳击”——或者甚至只是创建你自己的类似Nullable
的类型,这将是一个class
而不是一个非常hacky 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
做同样的System.Nullable
不会; 即使只是做new int?(42).GetType()
给你System.Int32
而不是System.Nullable<System.Int32>
。
System.Nullable
不是真正的类型 - 它会受到运行时的特殊处理。 它做了一些你根本无法用你自己的类型复制的东西,因为 hack 甚至不在 IL 的类型定义中——它就在 CLR 本身中。 另一个不错的抽象泄漏是可空类型不被视为struct
- 如果您的泛型类型约束是struct
,则不能使用可空类型。 为什么? 好吧,添加此约束意味着使用例如new T?()
是合法的 - 否则这是不可能的,因为您无法使可空性变为可空性。 使用我在此处编写的MyNullable
类型很容易,但使用System.Nullable
不然。
编辑:
CLI 规范的相关部分(1.8.2.4 - 装箱和拆箱一个值):
所有的值类型都有一个叫做 box 的操作。 装箱任何值类型的值都会产生其装箱值; 即,包含原始值的按位副本的相应装箱类型的值。 如果值类型是可空类型(定义为值类型 System.Nullable 的实例化),则结果是空引用或其类型 T 的 Value 属性的按位副本,具体取决于其 HasValue 属性(分别为 false 和 true) . 所有装箱类型都有一个称为取消装箱的操作,它会产生一个指向值的位表示的托管指针。
因此,根据定义,可空类型上的box
操作会产生空引用或存储值,而不是“装箱可空”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.