When looking over someone’s test code the other day, I happened to notice he was unboxing a boxed enum to an int. I was mildly surprised that he was able to do so – I thought you could only ever unbox to the exact value type that was “in the box”. Naturally, I consulted the spec. The C# spec is relatively woolly on the subject, unfortunately, but the CLI spec (partition three, end of chapter 4 for those who wish to look it up) is very clear. Here’s what it states under the Exceptions section, which is the interesting part – obj is the value to unbox, valuetype is the type we’re tring to unbox to:
InvalidCastException is thrown if obj is not a boxed valuetype (or if obj is a boxed enum and valuetype is not its underlying type)
|
That’s from the first edition of the spec. The third edition (still under consideration for approval by ISO/IEC) has this:
System.InvalidCastException is thrown if obj is not a boxed valuetype, or if obj is a boxed enum and valuetype is not its underlying type.
|
Seems harmless enough, right? Well, consider a situation where you have two enums, FirstEnum
and SecondEnum
. Then consider this (the code is in C#, but the generated IL is a direct translation – in particular, the types used for unboxing are preserved):
// Keep declarations out of the way to focus on the conversions. object o; int i; FirstEnum x; SecondEnum y; o = 1; // The value of o is a boxed System.Int32 i = (int) o; // Conversion 1 x = (FirstEnum) o; // Conversion 2 y = (SecondEnum) o; // Conversion 3 o = (FirstEnum)1; // The value of o is a boxed FirstEnum i = (int) o; // Conversion 4 x = (FirstEnum) o; // Conversion 5 y = (SecondEnum) o; // Conversion 6 |
Now, which of those versions should succeed? Reading absolutely literally, only conversion 1 should work. The others should all throw InvalidCastException
. In all but conversions 1 and 4, the first part of the “or” clause in the spec is true (i.e. we’re trying to unbox to a different type) and in conversion 5, the second part of the “or” clause is true (the value is a boxed enum, and the type we’re trying to unbox it to isn’t the underlying type – it’s the real type!).
I know that’s reading the spec very literally, rather than taking the obviously intended meaning – but specifications should be precise documents. In fact, I’m not sure the intended meaning is so obvious anyway. Clearly conversions 1 and 5 should work (an “exact match” should always be valid) but what about 4? Should I be able to unbox an enum to an int? The way the spec is worded suggests that probably I should. What about the other way round (conversions 2 and 3), unboxing an int to an arbitrary enum which has int as its underlying type? Not so sure about that. As for conversion 6, unboxing one enum to a completely different one (which happens to share the same underlying type) – I think I’d actually rather that failed.
So, what happens? Under the current .NET implementations, all the conversions succeed. That pretty much means that’s the way the behaviour is going to have to stay, meaning I won’t be able to get my wish about conversion 6. Still, never mind – what’s important is that the spec matches reality. I believe it’s very hard to word this in the “single sentence” style they’ve tried for. I think I’d say:
|