如何确定运行时对象是否为可空类型

时间:2015-04-07 12:51:15

标签: c# reflection nullable

首先:这不是How to check if an object is nullable?的副本。或者,至少,该问题没有提供有用的答案,作者的进一步阐述实际上询问了如何确定给定的类型(例如,从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参数)是否可以为空?把我吹走了:))

1 个答案:

答案 0 :(得分:2)

嗯,你来不及了。盒装值不再具有您搜索的类型信息 - 将int?值装箱null或装箱int。在某种程度上,它类似于在GetType上调用null - 它没有真正意义,没有类型信息。

如果可以的话,尽可能坚持使用泛型方法而不是装箱(dynamic对实际可空值和object s之间的某些接口非常有帮助。如果你不能,你将不得不使用你自己的&#34;拳击&#34; - 甚至只是创建自己的Nullable - 类型,这将是class而不是非常hacky struct - 类似的事情:D

如果需要,您甚至可以将Nullable类型设为struct。它不是打破类型信息的struct - 它不是破坏该信息的装箱本身,它是使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<>); } 的CLR黑客攻击匹配非可空值的性能。它非常聪明且非常有用,但它打破了一些基于反射的黑客攻击(就像你正在尝试做的那样)。

这可以按预期工作:

System.Nullable

new int?(42).GetType()执行相同的操作并非如此;即使只是System.Int32,也会为您System.Nullable<System.Int32>而不是System.Nullable

struct不是真正的类型 - 运行时会得到特殊处理。它确实可以用你自己的类型进行复制,因为即使在IL中的类型定义中黑客也没有 - 它在CLR本身就是正确的。另一个很好的抽象泄漏是可空类型不被视为struct - 如果您的泛型类型约束是new T?(),则您不能使用可空类型。为什么?好吧,添加这个约束意味着使用例如合法的MyNullable - 否则无法实现,因为你无法成为可空的可空。我在这里写的System.Nullable类型很容易,但box没有。

修改

CLI规范的相关部分(1.8.2.4 - 装箱和拆箱值):

  

所有值类型都有一个名为box的操作。拳击任何值   value类型产生其盒装值;即,值的   相应的盒装类型,包含原始的按位副本   值。如果值类型是可空类型 - 定义为   实例化值类型System.Nullable-结果是a   类型为T的Value属性的null引用或按位副本,   取决于其HasValue属性(分别为false和true)。所有   盒装类型有一个名为unbox的操作,这会导致托管   指向值的位表示的指针。

因此,根据定义,对可空类型的{{1}}操作会产生空引用或存储的值,而不是&#34;盒装可为空的&#34;。