Why does unboxing require explicit casting in C#?
Asked Answered
I

7

20

Boxing is the process of converting a value type into a managed heap object, which is implicit. Unboxing is the reverse process, for which the compiler requires an explicit cast. Since boxing stores the data type, why can't unboxing use that instead of asking for an explicit cast?

class BoxUnBox
{
 static void Main()
 {
   int i = 123;      // a value type
   object o = i;     // boxing
   int j = (int)o;   // unboxing - Why is an explicit cast required?
 }
}
Indiscrete answered 22/2, 2017 at 11:48 Comment(8)
Maybe because its unsafe? You can have anything stored in objectScorcher
how about "o as int"? i know its still explicit, but its often prettier than a castSexcentenary
@Sexcentenary o as int doesn´t work because int is a value-type which can´t be used in conjunction with an as-cast. You may however use o as int?.Grounds
@HimBromBeere good catch, always great learning something new. only now the different syntax is explained to me. i love this community.Sexcentenary
It doesn't have anything to do with unboxing, a downcast always requires an explicit cast. It is risky so you have to let the compiler know what you are doing is intended. Just declare the i variable as long to see it blowing up. Now do it correctly, int j = Convert.ToInt32(o);Sedlik
Although the boxed type is known at run-time, it is unknown at compile-time. So you have to tell the compiler what you expect to pop out of the box.Gibun
Your next question is highly likely to be "OK, why does the unboxing cast have to be the exact type of the boxed object?" That is, if I can cast an int to long, why can't I unbox a boxed int by casting to long? I encourage you to think about that question for a while, and then read ericlippert.com/2009/03/03/representation-and-identityCarmichael
@EricLippert: Eh, I don't buy it. C# could've easily supported this for native types without any performance overhead... e.g. on x86 you just need to store the boxed value as long long and then, when it's time to unbox, you just copy however many bytes you need to into the variable. And type checking could've just amounted to typecode1 <= typecode2 if you'd designed it right.Africanize
S
42

Your question is not related to unboxing operation only. Actually it should sound as "Why should I use explicit conversion?" Consider following example:

int i = 123;
long l = i;
int j = (int)l; // OMG why??

The answer is simple and you can find it in C# specification 6.2 Explicit conversions:

The explicit conversions are conversions that cannot be proven to always succeed, conversions that are known to possibly lose information, and conversions across domains of types sufficiently different to merit explicit notation.

In example above you might lose information, because long can hold values which do not fit int range. But you will never lose information when assigning int to long:

long l = i; // safe

In your example you need explicit conversion because implicit conversion cannot be proven to always succeed. Variables of object type can reference literally any type. What about string?

object o = i;  // implicit and always safe
o = "Now I have a machinegun ho-ho-ho"; // safe too
int j = o;     // will not succeed if o is string

Analogy

Object variable is like a black box where you can put anything - music CD, pen, phone or banana. Not only you, but anyone can put something there. If the last thing which you put in a black box in the morning was a banana, can you come back in the evening and eat whatever you pull out from the black box? If you live alone, and room is closed, and your memory is excellent, and... then you can. And you will wonder why everybody checks their box's content before eating it. But if you are not live alone, or the room is not closed, or you can forget just once that you have put the phone into the box... Bon appetite

Scorcher answered 22/2, 2017 at 12:0 Comment(2)
The conversion from long to int meets the criteria for explicitness in two ways: in an unchecked context, you possibly lose information; in a checked context, the conversion can fail on an OverflowException. (Programmers often seem to forget C# has checked arithmetic, and indeed it's not the default.)Sculpin
Very nice analogy! A nice way to explain the ideaPorfirioporgy
G
6

What if someone changes the content of o to say "Hello World". To be sure you know what you're doing, the compiler requires you to explicitly cast the boxed value.

Basically an implicit conversion implies that any instance o, of type object, can also be represented as an instance of int which clearly isn't the case. Consider for example this:

int i = -1;
long j = i;

It is clear that your variable i, which is an integer, can also be considered as long. This is why implicit casting is accurate here. On the other side, not every long is also convertable to int without any loss of data. Thus you need an explicit cast to determine: I know there might be some loss of data, however I don't care about it.

Grounds answered 22/2, 2017 at 11:53 Comment(0)
E
5

Because an Int32 is an Object, but an Object may be an Int32. The compiler knows what to do in the first case, but you have to tell the compiler you know what you're doing in the second case, and guarantee that the unboxing can be performed.

The inheritance relationship is directional! Parent is different from child.

Engelbert answered 22/2, 2017 at 11:54 Comment(12)
no, Int32 is a value-type and thus not an object. However you can convert it to `object*, which is what boxing means.Grounds
This is C#, not Java. EVERYTHING inherits from object, even primitive types. See https://mcmap.net/q/661827/-is-int-int32-considered-an-object-in-net-or-a-primitive-not-int.Engelbert
@him "Int32 is an alias for int": technically false: int in C# is an alias for System.Int32, that is a .NET "special" struct... The rest of the sentence is ok.Screwworm
Int32 is an alias for int and thus not an object is strictly wrong.Engelbert
@A.Chiesa It is right and wrong... Why technically everything inherits from object, for value types the inheritance is "strange"Screwworm
@xanatos: you're right. The implementation has some caveats, being highly optimized for performance. From a programmers perspective, it's usually more useful to think about Int32 being a struct, and struct being a class with some memory management differences. This sentence strictly IMO.Engelbert
@Screwworm Technically everything doesn't inherit from object. Pointers don't inherit from object, nor does Void.Quasijudicial
@Servy: if you want to get that technical -- Void is just an artifact of System.Reflection. It is a "privileged struct" in much the same way Int32 is. Methods/functions that return nothing do not, in fact, return Void. Void inherits from ValueType, which in turn inherits from, of course, Object.Sculpin
@JeroenMostert Void is a type because the specs define it as a type. Void is not an object because the specs also say as much. The details of the actual implementation surrounding it can't change that though. Those statements are true because they're defined to be true.Quasijudicial
@Quasijudicial - are you sure void is defined as a type? I can't find this definition, when searching in the specification. I'm just curious ;)Engelbert
@Servy: having combed both the C# and CLI specs I'm pretty sure the issue is more subtle than you state, but also moot, and certainly too far off topic to continue discussing. It's much ado about Void (and void, which is almost but not quite the same thing.) The case for pointers is far more straightforward (and has practical consequences).Sculpin
@A.Chiesa: the C# spec defines typeof(void) as System.Void (the latter is defined in the CLI spec as well), and states "a method's return type is void if it does not return a value", thereby implicitly making it a type (or at least a return type). The CLI spec is more subtle on the matter, but in C#, void is a type. Ish. (It's still true that methods that are declared as returning void do not return a value, and in particular, they do not return instances of Void, as no value can ever be of type Void.) See also.Sculpin
C
4

Any int is convertible to an object. Not all objects can be cast to ints.

Commutual answered 22/2, 2017 at 11:53 Comment(0)
B
3

The compiler cannot guarantee what is inside of your object. That is why you need to explicitly cast as the value you expect. For the compiler:

This is as hazardous

object o = 45;
int j = (int)o;

as this:

object o = "something";
int j = (int)o;

And that cannot be allowed at compile time.

Borreri answered 22/2, 2017 at 11:59 Comment(0)
H
2

Casting might fail at runtime, depending on what your object really contains. When implicit unboxing would be possible, you might overlook errors as you may have written something which was meant differently (either by you or you misunderstood someone else's code). The compiler requires you to cast explicitly because you should really want that cast. Otherwise you might mistakenly mix types, which produces verry error prone code. By beeing forced to cast explicitly you are forced to think twice if what you do is right.

Hypanthium answered 22/2, 2017 at 12:0 Comment(0)
M
1

Use of Dynamic avoids the cast

Not to take away from anything that has been said, but I wanted to point out that, technically, the cast is not always needed to perform the unboxing. The dynamic keyword allows for the system to perform the unboxing and conversion automagically. I am neither recommending nor discouraging the use of dynamic, merely pointing out it's behavior.

static void DynamicTest()
{
    int i = 123;      // a value type
    object o = i;     // boxing
    dynamic d = o;    // shift to dynamic
    int j = d;        // unboxing - No cast required
}

Edit: Jeroen Mostert wisely points out that the dynamic keyword is not any kind of magic that always makes this work. It merely defers evaluation to a runtime behavior. So, while the above example will always work, more complex examples will definitely fail. Therefore, one must take care when using the dynamic keyword and expect (try/catch) runtime failures. The dynamic keyword can, none-the-less be a very powerful tool, if used judiciously.

Maxwell answered 22/2, 2017 at 13:36 Comment(1)
You should mention that dynamic always defers looking up the appropriate conversion to runtime. That is its behavior; it's not a magic conversion system. (In particular, if in the snippet above long i = 123 is used, the code will compile fine but fail at runtime because there's no implicit conversion from long to int.)Sculpin

© 2022 - 2024 — McMap. All rights reserved.