I seemed to have stumbled upon something unusual within C# that I don't fully understand. Say I have the following enum defined:
public enum Foo : short
{
// The values aren't really important
A,
B
}
If I declare an array of Foo
or retrieve one through Enum.GetValues
, I'm able to successfully cast it to both IEnumerable<short>
and IEnumerable<ushort>
. For example:
Foo[] values = Enum.GetValues( typeof( Foo ) );
// This cast succeeds as expected.
var asShorts = (IEnumerable<short>) values;
// This cast also succeeds, which wasn't expected.
var asUShorts = (IEnumerable<ushort>) values;
// This cast fails as expected:
var asInts = (IEnumerable<int>) values;
This happens for other underlying types as well. Arrays of enums always seem to be castable to both the signed and unsigned versions of the underlying integral type.
I'm at a loss for explaining this behavior. Is it well-defined behavior or have I just stumbled onto a peculiar quirk of the language or CLR?
It's not just
IEnumerable<T>
- you can cast it to the array type as well, so long as you fool the compiler first:The C# language provides no expectation of this conversion being feasible, but the CLR is happy to do it. The reverse is true too - you could cast from a
short[]
to aFoo[]
. Oh, and if you had another enum with the same underlying type, you could cast to that, too. Basically, the CLR knows that all of these types are just 16-bit integers - all bit patterns are valid values, even though they'll have different meanings - so it lets you treat one type as another. I don't think that's documented in the CLI specification - I couldn't find any reference to it, anyway.This can cause some interesting problems when optimizations in LINQ (in
Cast<>
andToList
) are combined, just for the record.For example:
You might expect this to come up with an
InvalidCastException
(as it does if you start with aList<int>
with any values, for example) but instead you end up with:That's because the
Cast
operation has checked and found that theint[]
is already auint[]
, so it's passed it right along toToList()
, which then tries to useArray.Copy
, which does notice the difference. Ouch!