3

First: this is not a duplicate of How to check if an object is nullable?. Or, at least, there was no useful answer provided to that question, and the author's further elaboration actually asked how to determine whether a given type (eg. as returned from MethodInfo.ReturnType) is nullable.

However, that is easy. The hard thing is to determine whether a runtime object whose type is unknown at compile time is of a nullable type. Consider:

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);
}

This does not work as intended, because the GetType() call results in a boxing operation (https://msdn.microsoft.com/en-us/library/ms366789.aspx), and will return the underlying value type, rather than the nullable type. Thus IsNullable() will always return false.

Now, the following trick uses type parameter inference to get at the (unboxed) type:

private bool IsNullable<T>(T obj)
{
    var type = typeof(T);
    return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}

This seems initially promising. However, type parameter inference only works when the type of the object is known at compile time. So:

public void Foo(object obj)
{
    int? genericObj = 23;

    var bar1 = IsNullable(genericObj);   // Works
    var bar2 = IsNullable(obj);          // Doesn't work (type parameter is Object)
}

In conclusion: the general problem is not to determine whether a type is nullable, but to get at the type in the first place.

So, my challenge is: how to determine if a runtime object (the obj parameter in the above example) is nullable? Blow me away :)

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
Tor Haugen
  • 19,509
  • 9
  • 45
  • 63
  • 9
    By the time you've got the value as `object`, you've got a boxed value anyway. You need to remove that boxing operation. Unfortunately, we don't know what the context is here, which makes it hard to give an answer... if you *only* have a variable of type `object`, you've lost the relevant information already. – Jon Skeet Apr 07 '15 at 12:54

1 Answers1

2

Well, you're too late. A boxed value no longer has the type information you seek - boxing a int? value results either in null, or a boxed int. In a way, it's an analogue to calling GetType on null - it doesn't really make sense, there's no type information.

If you can, stick with generic methods instead of boxing as much as possible (dynamic can be very helpful for some of the interfaces between actual nullable values and objects). If you can't, you'll have to use your own "boxing" - or even just creating your own Nullable-like type, that will be a class rather than a very hacky struct-like thing :D

If needed, in fact, you can even make your Nullable type a struct. It's not the struct-ness that breaks the type information - it's not the boxing itself that destroys that information, it's the CLR hacks that enable Nullable to match the performance of non-nullable values. It's very smart and very useful, but it breaks some reflection-based hacks (like the one you're trying to do).

This works as expected:

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<>);
}

Doing the same with System.Nullable doesn't; even just doing new int?(42).GetType() gives you System.Int32 instead of System.Nullable<System.Int32>.

System.Nullable not a real type - it gets special treatment by the runtime. It does stuff that you simply can't replicate with your own types, because the hack isn't even in the type definition in IL - it's right in the CLR itself. Another nice abstraction leak is that a nullable type is not considered a struct - if your generic type constraint is struct, you can't use a nullable type. Why? Well, adding this constraint means it's legal to use e.g. new T?() - which wouldn't be possible otherwise, since you can't make a nullable of a nullable. It's easy with the MyNullable type I've written here, but not with the System.Nullable.

EDIT:

The relevant part of the CLI specification (1.8.2.4 - Boxing and unboxing a value):

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.

So by definition, the box operation on nullable types produces either a null reference or the stored value, never a "boxed nullable".

Luaan
  • 62,244
  • 7
  • 97
  • 116