4

Coming from this question why does n.GetHashCode() work but n.GetType() throws and exception? the answer Jon gave led me to this question: Why isn't Nullable<> hiding GetType:

public new Type GetType()
{
    return GetValueOrDefault().GetType();
}

Because then this

int? i = null;
Console.WriteLine(i.GetType().Name);

should work, shouldn't it? Am I missing something obvious? What are the caveats? I tried google but didn't find any satisfactory explanation.

Update: To clarify: a bit. This works:

int? i = null;
Console.WriteLine(i.GetHashCode());

The only reason why i.GetType() throws is because GetType is not virtual and could not be overridden. So when calling it i gets boxed into object which result in null and then it throws. However if Nullable would be implemented like this

 public struct Nullable<T> where T : struct
 {
     ....
     public new Type GetType()
     {
         return GetValueOrDefault().GetType();
     }
 }

Then it would make the behaviour more consistent (imho) in the regards that all of this would work instead of just the first two calls:

 int? i = null;
 Console.WriteLine(i.GetHashCode());
 Console.WriteLine(i.ToString());
 Console.WriteLine(i.GetType());
Community
  • 1
  • 1
ChrisWue
  • 18,612
  • 4
  • 58
  • 83
  • 2
    How would you hide a method that inherits from `Object`? – Kevin Apr 05 '11 at 09:21
  • 1
    Furthermore, why would you want to? – Grant Thomas Apr 05 '11 at 09:27
  • 3
    yeah, indeed, why would 'null.GetType()' throw null reference exception :) – Poma Apr 05 '11 at 09:55
  • 1
    `int?` is syntactic sugar for `Nullable` which is a value type. The only reason you can assign `null` to it is because there is special behaviour implemented in the CLR. So `i.GetType()` is technically **not** the same as `null.GetType()`. It ends up resulting in it due to the reason described above and in the question I linked to althoug I think it could have been avoided - but it wasn't. Therefor my question. – ChrisWue Apr 05 '11 at 10:24

2 Answers2

2

I think it's because GetType returns the exact runtime type of the current instance. In this case it has no runtime type because it references null.

Consider this example:

  MyBaseClass myBase = null;
  MyDerivedClass myDerived = null;
  object o = myDerived;
  MyBaseClass b = myDerived;

If myBase.GetType() would return MyBaseClass and myDerived.GetType() would return MyDerivedClass, then what should o.GetType() and b.GetType() return?

The reason Nullable doesn't simply hide object.GetType and return its compile time type when it is null, is probably because it would break GetType's contract.

Rian Schmits
  • 3,096
  • 3
  • 28
  • 44
  • As explained above `int?` is the same as `Nullable` and when you call `GetType` on a `Nullable` it will return `typeof(T)`. That's the existing behaviour. – ChrisWue Apr 05 '11 at 10:27
  • @ChrisWue No it doesn't return typeof(T), it calls `GetType` inherited from `object` and returns its runtime type. See: http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx. Maybe you're confused with `Type.GetType`? – Rian Schmits Apr 05 '11 at 10:38
  • Ok, maybe I expressed it not quite right but `GetType` returns the underlying type when called on `Nullable`. – ChrisWue Apr 05 '11 at 20:03
  • This answer is not good. It says _it has no runtime type because it references `null`._ but that is incorrect. It's a value type, there is an object with a runtime type. Also, to convince yourself that the current behavior cunfuses many, see the number of questions linked to the thread [Nullable type is not a nullable type?](http://stackoverflow.com/questions/785358/). The call kind-of gives the wrong answer today, but there's no real need to make this `GetType()` call for a value type. See my new answer. – Jeppe Stig Nielsen Apr 29 '13 at 22:23
0

I think the reason why they didn't do that is that it is generally a bad idea to hide an inherited method with a new method with the same name and signature.

If they chose to actually hide Object.GetType(), the question is if they should return the type of the Nullable<T> struct, or the underlying type. This would be either:

public struct Nullable<T> where T : struct
{
    ....
    public new Type GetType()
    {
        return typeof(Nullable<T>);
    }
}

or:

public struct Nullable<T> where T : struct
{
    ....
    public new Type GetType()
    {
        return typeof(T);
    }
}

If you have:

int? i = null;

or:

int? i = 42;

the "real" runtime type of i is certainly Nullable<int> (also known as int?). Of course, introducing a new GetType method now (in .NET version 5.0, say) which returned Nullable<int> would be a breaking change. That's because the way it is today, the i will be boxed into either a null reference or a boxed int (not a boxed Nullable<int>) because of the magical boxing of Nullable<>.

But: There is really no reason for a (capable) programmer to ever use .GetType() on a variable (or other expression) of compile-time type T?, because he knows the actual type is the same as the compile-time type, namely typeof(T?). He also knows that the underlying type is typeof(T).

For the same reason, for a "usual" (not nullable) value type T, it is not useful for a programmer to use .GetType() when the compile-time type is T, because he knows the result will always be typeof(T). This is because all value types are sealed types. Also, for a for a reference type which is sealed (and for which no type parameter is co- or contravariant), .GetType() is useless.

To give an example:

string str = ...;
...
var t = str.GetType(); // This is really useless. If str is null
                       // an exception is thrown. Otherwise the
                       // t will ALWAYS be typeof(string) because
                       // the class System.String is a sealed class.

The base classes of the Nullable<> struct are System.ValueType and System.Object, and Nullable<> implements no interfaces. But if our i from before is cast and put into a variable of compile-time type ValueType or object (or dynamic), it loses its identity of Nullable<int> and becomes an ordinary boxed value type. Therefore, even if Object.GetType() were made virtual (which would of course be extremely dangerous) it wouldn't even be helpful.

Conclusion: The runtime type is Nullable<> if and only if the compile-time type is Nullable<>, so this "problem" is not interesting to fix.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181