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

首先:这不是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参数)是否可以为空?把我吹走:)

解决方法:

好吧,你来不及了.盒装值不再具有您寻找的类型信息 – 装箱一个int? value结果为null或boxed int.在某种程度上,它类似于在null上调用GetType – 它没有真正意义,没有类型信息.

如果可以的话,尽可能坚持使用泛型方法而不是装箱(动态对于实际可以为空的值和对象之间的某些接口非常有帮助).如果你不能,你将不得不使用你自己的“拳击” – 甚至只是创建你自己的Nullable类型,这将是一个类而不是一个非常hacky结构类的东西:D

事实上,如果需要,您甚至可以将Nullable类型作为结构.它不是破坏类型信息的结构 – 它不是破坏信息的拳击本身,而是使Nullable能够匹配非可空值的性能的CLR黑客.它非常聪明且非常有用,但它打破了一些基于反射的黑客攻击(就像你想要做的那样).

这按预期工作:

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执行相同操作不会;甚至只是做新的int?(42).GetType()给你System.Int32而不是System.Nullable< System.Int32>.

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

编辑:

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

All value types have an operation called box. Boxing a value of any
value type produces its boxed value; i.e., a value of the
corresponding boxed type containing a bitwise copy of the original
value. If the value type is a nullable type—defined as an
instantiation of the value type System.Nullable—the result is a
null reference or bitwise copy of its Value property of type T,
depending on its HasValue property (false and true, respectively). All
boxed types have an operation called unbox, which results in a managed
pointer to the bit representation of the value.

因此,根据定义,可空类型的box操作产生空引用或存储值,从不产生“盒装可空”.

上一篇:浅谈Spring之@Nullable、@NonNull注解


下一篇:c# – 可空属性的映射结果为0但不是Null