18

why is typeof int? an Int32

int? x = 1;
Console.WriteLine(x.GetType().Name);

If it is okay then what's the use of Nullable.GetUnderlyingType?

Timo Tijhof
  • 10,032
  • 6
  • 34
  • 48
Mubashir Khan
  • 1,464
  • 1
  • 12
  • 17
  • 1
    I think the question is why does the code write out "Int32" instead of "Nullable" – Martin Brown Dec 23 '10 at 11:01
  • 1
    I think what he means is this: If calling `x.GetType().Name` can get you `Int32` in above example, why do we need Nullable.GetUnderlyingType() to find out that its `Int32`. – decyclone Dec 23 '10 at 11:02
  • 1
    The assumption is false - `typeof(int?)` is `System.Nullable`. – IS4 Feb 23 '16 at 14:13

5 Answers5

28

Calling GetType() boxes your variable. The CLR has a special rule that Nullable<T> gets boxed to T. So x.GetType will return Int32 instead of Nullable<Int32>.

int? x = 1;
x.GetType() //Int32
typeof(int?) //Nullable<Int32>

Since a Nullable containing null will be boxed to null the following will throw an exception:

int? x = null;
x.GetType() //throws NullReferenceException

To quote MSDN on Boxing Nullable Types:

Objects based on nullable types are only boxed if the object is non-null. If HasValue is false, the object reference is assigned to null instead of boxing

If the object is non-null -- if HasValue is true -- then boxing occurs, but only the underlying type that the nullable object is based on is boxed. Boxing a non-null nullable value type boxes the value type itself, not the System.Nullable<T> that wraps the value type.

Community
  • 1
  • 1
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • @PeterLillevold I think that's done (maybe after your comment though) but in any case, SO is a collaborative environment - you are more than welcome to edit an answer (or question) yourself and format it. – Ofer Zelig Nov 15 '16 at 23:53
  • 1
    @OferZelig yes, I'm well aware of that. I think back in 2010 I just wanted to give CodesInChaos the chance to do it :) – Peter Lillevold Nov 16 '16 at 07:06
11

This example is a bit confused, because:

int? x = 1;

creates a Nullable<int> like you expect; however:

Type type = x.GetType();

is a call to a non-virtual method on object, which isn't (and can't be) overridden - therefore this is a boxing operation; and Nullable<T> has special boxing rules:

  • if it is empty, it boxes to null
  • if it has a value, the value is boxed and returned

i.e.

int? x = 1;
int y = 1;

box to exactly the same thing.

Therefore, you are passing typeof(int) to GetUnderlyingType.

A more illustrative example of when this helps is when using reflection:

class Foo {
    public int? Bar {get;set;}
}
...
Type type = typeof(Foo); // usually indirectly
foreach(var prop in type.GetProperties()) {
     Type propType = prop.PropertyType,
          nullType = Nullable.GetUnderlyingType(propType);

     if(nullType != null) {
         // special code for handling Nullable<T> properties;
         // note nullType now holds the T
     } else {
         // code for handling other properties
     }
}
Noctis
  • 11,507
  • 3
  • 43
  • 82
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 4
    `GetUnderlingType` - my favorite typo of the day. Also, `tupe`. – BoltClock Dec 23 '10 at 11:07
  • @Marc: Actually, `GetType` is **not** `virtual` and that's why it requires boxing (it calls the implementation of `Object.GetType` and that requires the receiver to be of type `Object`). If it were virtual, and was overridden in the derived class, it wouldn't require boxing. – Mehrdad Afshari Dec 23 '10 at 11:09
  • @Mehrdad - I was unclear in my terminology; it *is* a virtcall, but it is to a non-virtual method that resolves to object hence needs boxing. As would a virtcall to a virtual method that hasn't been overridden. I know you know that - I'm just adding full context ;p – Marc Gravell Dec 23 '10 at 11:11
  • you mean that x.GetType() and x.Value.GetType() are the same? strange isnt it. and when you do reflection on some method returning nullable type, you get something like Nullable'1 – Mubashir Khan Dec 23 '10 at 11:12
  • @Mubashir if you think *that's* strange, it has another consequence: http://stackoverflow.com/questions/194484/whats-the-strangest-corner-case-youve-seen-in-c-or-net/194671#194671 – Marc Gravell Dec 23 '10 at 11:13
  • @Mobashir: There's a slight difference between the two if the object is `null`. In the former, the object will be boxed and the method will be called on the resulting `null` reference, which causes the *runtime* to throw `NullReferenceException`, while in the latter, the `Value` property (which doesn't require boxing) will check and throws `InvalidOperationException` if the value is indeed null. – Mehrdad Afshari Dec 23 '10 at 11:16
  • One more thing, Nullable is a struct but allows null assignment. Nullable x = null; – Mubashir Khan Dec 23 '10 at 11:28
  • @Mubashir well, not quite - it *pretends* to - but that is actually the C# compiler changing the code for you (into something that isn't *really* null). It does this for most operations involving `Nullable`. – Marc Gravell Dec 23 '10 at 11:30
  • Afaik the boxing of `Nullable` to `T` is a feature of the runtime and not the C# compiler. – CodesInChaos Dec 23 '10 at 13:34
  • @CodeInChaos - do you mean to object? `T?` to `T` isn't boxing. – Marc Gravell Dec 23 '10 at 13:46
  • I mean that if I box a `Nullable` by casting to `(object)` it is a boxed `T` afterwards and not a boxed `T?`. And that the decision that is should become a boxed `T` and not a boxed `T?` is made by the runtime, and not because the C# compiler creates some extra code. – CodesInChaos Dec 23 '10 at 17:45
  • Mark is correct all methods against a generic method are called as `constrained !!T`, `callvirt Method` the reasoning is that they have to normalize the calling convention between struct and class types. However, in this case the normalization which normally prevents boxing cannot as `GetType` only exists on object as it is a non-virt method. When dealing in generic methods all conversion to object and interfaces are all spaced with a `box !!T` instruction too, which just gets compiled away into a `nop` for ref types. – Michael B Dec 23 '10 at 19:03
4

Its for when you don't know its Int32.

Example:

    public Type GetNullableUnderlyingType<T>(Nullable<T> obj) 
        where T : struct
    {
        return Nullable.GetUnderlyingType(typeof(Nullable<T>));
    }

Here, you can pass any Nullable object and get it to return it's underlying type.

Community
  • 1
  • 1
decyclone
  • 30,394
  • 6
  • 63
  • 80
  • 1
    The problem here is; to call that method in regular code, the generics must be resolved at the caller. Which means that the caller **already knows the type**. And (to match the signature) they must **already know** it is nullable etc. If they already know... why are they asking? – Marc Gravell Dec 23 '10 at 11:17
2

When you write int? it's as if you've written Nullable<int>. That's the type you're looking for, I think.

Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
1

Mainly its for dealing with a generic method:: e.g.

public static void SomeMethod<T>(T argument)
{
     if(Nullable.GetUnderlyingType(typeof(T) != null)
     {
             /* special case for nullable code go here */
     }
     else
     {
            /* Do something else T isn't nullable */
     }
}

It's important to know this, as certain things that are very cheap can be wildly expensive on nullable's. For instance, if(argument == null) is normally super cheap, but when done in a generic method on a Nullable<T> is forced to box the argument to get a null reference. Your best bet is to use EqualityComparer<T>.Default which will slow everything else down, but makes nullable's not suffer.

Michael B
  • 7,512
  • 3
  • 31
  • 57