繁体   English   中英

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM