Boxed nullable underlying type can be cast to enum but boxed enum type can't be cast to nullable type
Asked Answered
R

1

5
  • Boxed nullable underlying type can be cast to enum but boxed enum type can't be cast to nullable type.

And similarly,

  • Boxed nullable enum can be cast to underlying type but boxed underlying type can't be cast to nullable enum.

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:

  1. So what C# rule is letting this happen?

  2. Is there a simple way I can remember this?

Rend answered 18/12, 2013 at 10:23 Comment(1)
Good question. I think it's actually a difference at the CLR level rather than the C# level. There are various things which are valid at the CLR level, but aren't allowed at the C# level - like casting between int[] and uint[].Ohm
O
4

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 a Nullable<T> and obj is not a boxed T, 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.

Ohm answered 18/12, 2013 at 10:49 Comment(1)
Spec ninja strikes! :) Sorry for being late to respond. Indeed good find. Though I'm not entirely sure why it has been designed so, quite confusing.Rend

© 2022 - 2024 — McMap. All rights reserved.