And similarly,
Ok, I know "boxed nullable type" is not the best way to describe it, but it's for the sake of the question. I'm aware it's the underlying value type that's getting boxed.
I will show it with examples. Assume I have an enum
with int
as the underlying type.
enum Sex { Male, Female }
Case I:
int? i = 1;
object o = i;
Sex e = (Sex)o; //success
//but
Sex e = Sex.Male;
object o = e;
int? i = (int?)o; //invalid cast
Case II:
Sex? e = Sex.Male;
object o = e;
int i = (int)o; //success
//but
int i = 1;
object o = i;
Sex? e = (Sex?)o; //invalid cast
In a nutshell,
(enum)int? -> succeeds
(int?)enum -> the reverse fails
(int)enum? -> succeeds
(enum?)int -> the reverse fails
Or in even simpler terms,
cast to non-nullable -> succeeds
cast to nullable -> fails
Now I do know that once you box a value type, it can be cast back only to the original type. But since by C# rules, a boxed int
can be cast to enum
and a boxed enum
can be cast to int
, and a boxed int
to int?
and a boxed int?
to int
, I was looking for a consistent understanding of other scenarios as well, ie the ones listed above. But I dont get the logic. For one, I feel if they all failed or if they all succeeded, it made more sense to developers. Two, even the successful casts look a little odd. I mean since a value type can be implicitly cast to its nullable equivalent (and not the other way around), a cast to nullable should anyway succeed, but with the current implementation a nullable type is being successfully cast to non-nullable which can even fail if the former had a null value. Had this whole thing been other way around, it would have been easier comprehending. An example like:
Sex? e = null;
object o = e;
int i = (int)o; //succeeds, but sure to explode on cast
//but
int i = 1;
object o = i;
Sex? e = (Sex?)o; //invalid cast, even though its always a safe cast
Questions:
So what C# rule is letting this happen?
Is there a simple way I can remember this?
I think this is a subtlety of the unbox
and unbox.any
IL instructions.
From ECMA 335, section III.4.32 (unbox
operation - unbox.any
is similar)
Exceptions:
System.InvalidCastException
is thrown if obj is not a boxed value type, valuetype is aNullable<T>
and obj is not a boxedT
, or if the type of the value contained in obj is not verifier-assignable-to (III.1.8.2.3) valuetype.
So for example, in this case:
Sex e = Sex.Male;
object o = e;
int? i = (int?)o;
it fails entirely correctly - because valuetype is Nullable<int>
and the value of obj is not a boxed int
. The "verifier-assignable-to" part doesn't apply for the Nullable<T>
case.
I doubt that any of this behaviour is described in the C# specification, unfortunately - I don't think the unboxing behaviour from "boxed int
" to "enum with an underlying type of int
" is described, as far as I can see, which is a sort of prerequisite to then including nullability in the mix.
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