Enums in dictionary keys.

I was doing some random reflection walking to remind myself whether using a struct as a dictionary key triggered any boxing operations using the default equality comparer.  And if you implement IEquatable<T> it still executes a box statement in IL, but my attempts at creating an equality comparer which trivially does not execute boxing operations isn’t any faster, so it seems the optimiser can properly eliminate explicit null checks in generics where the type parameter ends up being a struct and remove the box operation as dead code.  But that is not what this post is about.

Along the way in my reflection I saw that the default equality comparer special cases enums.  I immediately thought this was great, because I had recently discovered that GetHashCode called on an enum is ‘expensive’, it is *much* cheaper if you cast it to the underlying type and call GetHashCode on that instead. (Specific case was an enum member of a type which overrode equality, so the default equality comparer special case didn’t apply.)  So I had a look at the reflected code for this special case enum equality comparer and found an ‘interesting’ thing.

The implementation called this thing called JitHelper.UnsafeEnumCast<T> to cast its parameters to int.  What makes it interesting is that the ‘implementation’ of this method is ‘throw new InvalidOperationException()’.  Wait what…

Now obviously this method implementation isn’t actually called by EnumEqualityComparer, using enum’s as dictionary keys works just fine.  So it seems that the JIT magically substitutes a cast if T is an enum type, and only uses the implementation as a default fallback scenario.

The things they do to get around the limited type constraints in generics…

And this led me to look closer at JitHelper.  Here I find JitHelper.UnsafeCast<T>…

Have you ever wanted to cast Generic<Specific> to Generic<T> when you know at runtime that T is Specific?  It seems that this little piece of magic lets you do that. (It is apparently used in async stuff to allow for cached result tasks to be returned for a bunch of basic types from a generic result task building function.  For bonus points I now know that true, false, 0 in a dozen types, null and the integers -1 through 9(?!?), each have their own special cached task instances to be shared…)