GetType() weirdness in .NET

Following up this question on stackoverflow I stumbled across some weird issues regarding GetType().

1. GetType() cannot be overridden but hidden

While GetType() is not virtual for very good reasons and therefor one cannot override it the following is possible:

class MyClass
{
    public new Type GetType()
    {
         return typeof(string);
    }
}

Not that this is a good idea but it compiles and runs:

var t1 = new MyClass().GetType();
var t2 = ((object)new MyClass()).GetType();
Console.WriteLine("t1 = {0} --- t2 = {1}", t1.Name, t2.Name);

results in the expected output:

t1 = String --- t2 = MyDisposable

Now if it so important that GetType() does not violate its contract then why hasn’t there been a rule added to the specification saying that you are not allowed to new GetType(). You could argue that GetType() is just a normal method like any other – however it isn’t really. There is a lot of code relying on the fact that it does what it does and is not changed at whim, except it’s still possible to break it under certain circumstances – why not prevent it alltogether? Another argument I guess is that it would assign some special meaning for the compiler to an implemented method on the framework which certainly is not a good idea, right? Well, there are at least two exceptions out there already. One is IDisposable where an interface has a special language construct (using in C#) which relies on it. The other one is Nullable which is the only value type you can assign null to. I admit that one should be careful in what exceptions to the rule are choosen however in the case of GetType() it might have been worth it. Now the latter of the two mentioned exceptions leads me to the second weirdness.

Nullable is only sometimes null

Coming from the linked question at the top, it is apparent that the following is a bit inconsistent:

int? i = null;
Console.WriteLine(i.GetHashCode()); // works
Console.WriteLine(i.ToString()); // works
Console.WriteLine(i.HasValue); // works
Console.WriteLine(i.GetType()); // NullReferenceException

The reason being that GetType() is not virtual and is not overridden and therefor i gets boxed into object resulting in null. So a Nullable set to null does not behave like a reference type set to null when it comes to calling methods on it – except for GetType(). Why that? We have already determined that you can hide GetType() so Nullable could have done just that to and avoided the null reference problem.

Maybe someone can shed some light on why some of the decisions have been as they stand.

Advertisements

Value types and null in C#

Recently I came across one of the edge cases in C#. I needed a method to combine a list of items to a string. So I wrote an extension method for that:

public static string StringJoin<T>(this IEnumerable<T> list, string separator, Func<T, string> converter)
{
    return string.Join(separator, list.Select(converter).ToArray());
}

Pretty simple: takes a separator and a delegate to convert objects of type T into string. Next thing was to add a convenience method using ToString as default converter.

public static string StringJoin<T>(this IEnumerable<T> list, string separator)
{
    return list.StringJoin(separator, x => x != null ? x.ToString() : null);
}

The null check is to make sure I don’t get unexpected null reference exceptions. However ReSharper gave me a warning about a possible comparison of value type with null. Furthermore it offered me the suggestion to replace it with default(T) and indeed when letting it do its magic it would convert it to

public static string StringJoin<T>(this IEnumerable<T> list, string separator)
{
    return list.StringJoin(separator, x => x != default(T)? x.ToString() : null);
}

which is wrong – it won’t compile.

So I got curious why the ReSharper guys went through the motions to not only warn about it (well – it’s a compiler warning, so I guess it’s fair enough) but to add a suggestion which leads to uncompilable code. I asked my favourite search engine about it and I came across a few articles about that “problem” all basically stating that’s how it is. And someone came up with a solution here: http://devnet.jetbrains.net/thread/293148?tstart=0

After writing some benchmark it turned out that in release mode the workaround was about 5 times slower than the simple comparison.

What do we learn from it this?

  1. Don’t optimize prematurely.
  2. Don’t try to fix what is not broken.
  3. Don’t try to outsmart the compiler – it might cost you in optimization potential.