Understanding values and references


Because a variable of type object can refer to a boxed copy of a value, it’s only reasonable to allow you to get at that boxed value through the variable. You might expect to be able to access the boxed int value that a variable o refers to by using a simple assignment statement such as this:

int i = o;

However, if you try this syntax, you’ll get a compile-time error. If you think about it, it’s pretty sensible that you can’t use the int i = o; syntax. After all, o could be referencing absolutely anything and not just an int. Consider what would happen in the following code if this statement were allowed:

Circle c = new Circle();
int i = 42;
object o;
o = c; // o refers to a circle
i = o; // what is stored in i?

To obtain the value of the boxed copy, you must use what is known as a cast. This is an operation that checks whether converting an item of one type to another is safe before actually making the copy. You prefix the object variable with the name of the type in parentheses, as in this example:

int i = 42;
object o = i; // boxes
i = (int)o; // compiles okay

The effect of this cast is subtle. The compiler notices that you’ve specified the type int in the cast. Next, the compiler generates code to check what o actually refers to at runtime. It could be absolutely anything. Just because your cast says o refers to an int, that doesn’t mean it actually does. If o really does refer to a boxed int and everything matches, the cast succeeds, and the compiler-generated code extracts the value from the boxed int and copies it to i. (In this example, the boxed value is then stored in i.) This is called unboxing. The following diagram shows what is happening:


On the other hand, if o does not refer to a boxed int, there is a type mismatch, causing the cast to fail. The compiler-generated code throws an InvalidCastException exception at runtime. Here’s an example of an unboxing cast that fails:

Circle c = new Circle(42);
object o = c; // doesn't box because Circle is a reference variable
int i = (int)o; // compiles okay but throws an exception at runtime

The following diagram illustrates this case:


You will use boxing and unboxing in later exercises. Keep in mind that boxing and unboxing are expensive operations because of the amount of checking required and the need to allocate additional heap memory. Boxing has its uses, but injudicious use can severely impair the performance of a program. You will see an alternative to boxing in Chapter 17, “Introducing generics.”