Normally, one would expect, and hope, that two casts are needed to first unbox a value type and then perform some kind of value type conversion into another value type. Here's an example where this holds:
// create boxed int
IFormattable box = 42; // box.GetType() == typeof(int)
// unbox and narrow
short x1 = (short)box; // fails runtime :-)
short x2 = (short)(int)box; // OK
// unbox and make unsigned
uint y1 = (uint)box; // fails runtime :-)
uint y2 = (uint)(int)box; // OK
// unbox and widen
long z1 = (long)box; // fails runtime :-)
long z2 = (long)(int)box; // OK (cast to long could be made implicit)
As you can see from my smileys, I'm happy that these conversions will fail if I use only one cast. After all, it's probably a coding mistake to try to unbox a value type into a different value type in one operation.
(There's nothing special with the IFormattable
interface; you could also use the object
class if you prefer.)
However, today I realized that this is different with enums (when (and only when) the enums have the same underlying type). Here's an example:
// create boxed DayOfWeek
IFormattable box = DayOfWeek.Monday; // box.GetType() == typeof(DayOfWeek)
// unbox and convert to other
// enum type in one cast
DateTimeKind dtk = (DateTimeKind)box; // succeeds runtime :-(
Console.WriteLine(box); // writes Monday
Console.WriteLine(dtk); // writes Utc
I think this behavior is unfortunate. It should really be compulsory to say (DateTimeKind)(DayOfWeek)box
. Reading the C# specification, I see no justification of this difference between numeric conversions and enum conversions. It feels like the type safety is lost in this situation.
Do you think this is "unspecified behavior" that could be improved (without spec changes) in a future .NET version? It would be a breaking change.
Also, if the supplier of either of the enum types (either DayOfWeek
or DateTimeKind
in my example) decides to change the underlying type of one of the enum types from int
to something else (could be long
, short
, ...), then all of a sudden the above one-cast code would stop working, which seems silly.
Of course, the enums DayOfWeek
and DateTimeKind
are not special. These could be any enum types, including user-defined ones.
Somewhat related: Why does unboxing enums yield odd results? (unboxes an int
directly into an enum)
ADDITION:
OK, so many answers and comments have focused on how enums are treated "under the hood". While this is interesting in itself, I want to concentrate more on whether the observed behavior is covered by the C# specification.
Suppose I wrote the type:
struct YellowInteger
{
public readonly int Value;
public YellowInteger(int value)
{
Value = value;
}
// Clearly a yellow integer is completely different
// from an integer without any particular color,
// so it is important that this conversion is
// explicit
public static explicit operator int(YellowInteger yi)
{
return yi.Value;
}
}
and then said:
object box = new YellowInteger(1);
int x = (int)box;
then, does the C# spec say anything about whether this will succeed at runtime? For all I care, .NET might treat a YellowInteger
as just an Int32
with different type metadata (or whatever it's called), but can anyone guarantee that .NET does not "confuse" a YellowInteger
and an Int32
when unboxing? So where in the C# spec can I see if (int)box
will succeed (calling my explicit operator method)?
The tilde (~) is a character in the standard ASCII character set that is provided on a conventional computer keyboard and is used in both writing and computer programming. It corresponds to ASCII code 126. The tilde is also sometimes known as the twiddle.
The logical OR operator ( || ) returns the boolean value true if either or both operands is true and returns false otherwise.
C operators are one of the features in C which has symbols that can be used to perform mathematical, relational, bitwise, conditional, or logical manipulations. The C programming language has a lot of built-in operators to perform various tasks as per the need of the program.
This operator first subtracts the value on the right from the current value of the variable on left and then assigns the result to the variable on the left. Example: (a -= b) can be written as (a = a - b) If initially value stored in a is 8. Then (a -= 6) = 2.
When you use:
IFormattable box = 42;
long z2 = (long)(int)box;
You are actually unboxing and then casting.
But in your second case:
IFormattable box = DayOfWeek.Monday;
DateTimeKind dtk = (DateTimeKind)box;
You don't perform any casting at all. You just unbox the value. The default underlying type of the enumeration elements is int.
Update to refer to the real question:
The specification you mentioned in the comment:
The explicit enumeration conversions are:
...
From any enum-type to any other enum-type.
This is actually correct. We cannot implicitly convert:
//doesn't compile
DateTimeKind dtk = DayOfWeek.Monday;
But we can explicitly convert:
DateTimeKind dtk = (DateTimeKind)DayOfWeek.Monday;
It seems that you found a case when this is still required. But when combined with unboxing, only explicit conversion needs to be specified and unboxing can be ommited.
Update 2
Got a feeling that somebody must have noticed that before, went to Google, searched for "unboxing conversion enum" and guess what? Skeet blogged about it in 2005 (CLI spec mistake with unboxing and enums)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With