Introduction
First of all, the purpose of writing this article should be noted. Experienced programmers understand how boxing and unboxing work, as well as how to solve the possible problems associated with these processes. However, successful explanation of these processes to beginners requires them to understand additional technologies. Thus, IL, stack, heap for example, may be hard to understand for them.
Developers should understand boxing and unboxing to avoid negative side effects that can appear in specific situations in software development for .NET platform. Boxing and unboxing can increase memory consumption and reduce performance. Moreover, these processes are often hidden and the programmer cannot control them explicitly.
There are tons of good explanatory materials on the internet and in books. This article is a trial to create one new additional conception how to explain to beginners mechanism of boxing and unboxing. The idea is to make simulation using simple C# code.
It is proposed to create a program that simulates the process of boxing and unboxing for a clear and explicit level. Although the creation of a model that is completely compliant to reality is impossible, the model that can show the key features of considered processes can be built quite easily. By its nature, simulation simplifies the actual situation and, therefore, discrepancy with reality in many cases is not a flaw.
COM Technology
C# OOP and variable model are influenced by previously developed COM model. Although COM is not an subject of this article, some features of COM should be mentioned:
- COM is a universal OOP supporting technology.
- COM does not depend on implementation (or usage) programming language. Thus COM has binary format.
- COM has binary format mirroring internal representation of C++ class. So, COM components are classes, not structures (while classes and structures are quite similar in C++, they are not the same).
- COM components are accessed only by interfaces.
- COM components support inheritance by containment and aggregation.
- COM components support polymorphism through interfaces.
- COM are binary constructions and their internals cannot be accessed nor modified.
Influence of COM to C# OOP and variable model are:
- Classes can be accessed only by reference or interface.
- Structures are not classes. Because pointers are prohibited in safe code, structures are accessed directly, but not by reference or interface.
- Structures can be wrapped to classes (to object). These wrappers are objects and can be accessed by reference or interface. This magic names boxing. Wrapped structures can be unwrapped, in other words, unboxed back to structures.
- Structures provide higher performance and smaller memory usage. Classes in their turn supports OOP/OOD. Boxing/Unboxing can be considered as the bridge connecting these two constructions and possibilities.
For more details, you can also see the following materials:
- Inheritance, Aggregation, and Containment
- “Programming Visual C++”, Microsoft Press, 1998
Simulation
As Wikipedia says: “Simulation is the imitation of the operation of a real-world process or system over time. The act of simulating something first requires that a model be developed; this model represents the key characteristics or behaviors/functions of the selected physical or abstract system or process. The model represents the system itself, whereas the simulation represents the operation of the system over time.” (see Simulation https://en.wikipedia.org/wiki/Simulation).
To understand why we need a model, we should pay attention that boxing and unboxing are internal .NET processes and cannot be controlled by the programmer directly. Programmer can control when boxing or unboxing take place only implicitly by using or avoiding certain C# codes. Thus, the main purpose of the simulation is to show hidden internal behavior according to C# code used.
Model of the System
The simple model can be built to simulate boxing and unboxing of type int
. Type int
is selected for modeling because it is the widely used simple type. Another type to be modeled is object
.
Boxing and unboxing are controlled and implemented by Compiler and Runtime. Compiler and Runtime perform some magic supporting boxing and unboxing. All the details should not be modeled because it is a very complicated task. At the same time, the magic of Compiler and Runtime can be explained by difference of standard code flow and the simulation (see chapter Use Cases below).
The system with these assumptions will contains three components:
- Model of
int
type - Model of
object
type - Compiler and Runtime – will not be modeled to avoid too complicated model
Model of int
Because int
is a value type, let’s create type IntValue
that is also structure. Type IntValue
will model type int
. The code contains comments. See Use Cases below to better understand how to use the code.
At the same time, the following features should be highlighted:
- At the low level, the structures are located in stack (or in CPU registers if optimized by the JIT), but not in heap. The structures are also accessed directly, but not by reference (thus, they are named as value types). So the structures cannot implement interfaces. Thus, we implemented method
CompareTo
as a simple member. (We cannot implement it as an IComparable.CompareTo
here because it will break the simulation model and put boxing to the compiler magic).
public struct IntValue
{
private int _v;
public IntValue(int value)
{
_v = value;
}
public static implicit operator IntValue(int value)
{
return new IntValue(value);
}
public static implicit operator int(IntValue value)
{
return value._v;
}
public override string ToString()
{
return _v.ToString();
}
public int CompareTo(object obj)
{
if (obj == null)
return 1;
if (obj is IntValue)
{
IntValue iv = (IntValue)obj;
if (_v < iv) return -1;
if (_v > iv) return 1;
return 0;
}
throw new System.ArgumentException("Type must be compatible with IntValue");
}
}
Model of Object
Because object
is a reference type, let’s create type IntObject
that is a class. Type IntObject
models type int
that boxed to object
. The code contains comments. See Use Cases below to better understand the code.
At the same time, the following features should be highlighted:
- Simulation of boxing is modeled as conversion from
IntValue
to IntObject
. Because boxing is an implicit process, it is modeled by implicit operator. - Simulation of unboxing is modeled as conversion from
IntObject
to IntValue
. Because unboxing is an explicit process, it is modeled by explicit operator. - Boxed types internally (at low level) are constructions of
object
type. But they return type of wrapped values. So GetType()
implements this logic. - Classes are located in heap and accessed by the reference. Thus, they can implement and provides interfaces natively (see COM Technology above). By this way,
IntObject
is implemented in a standard way through IComparable.CompareTo
calling simple member CompareTo
of IntValue
.
IntObject
specializes working with int
type only. This limitation simplifies the model. Although object
is universal for all value types, we could also build universal model for object
, but it will be too complex to develop and explain boxing and unboxing.
public class IntObject: IComparable
{
private IntValue _v;
private IntObject(IntValue value)
{
_v = value;
}
public static implicit operator IntObject(IntValue value)
{
Console.WriteLine("Boxing " + value.ToString());
return new IntObject(value);
}
public static explicit operator IntValue(IntObject value)
{
Console.WriteLine("UnBoxing " + value._v.ToString());
return value._v;
}
public Type GetType()
{
return _v.GetType();
}
public int CompareTo(object obj)
{
return _v.CompareTo(obj);
}
}
Use Cases
Boxing and unboxing can be studied by the following technique:
- Replace all
int
entries by IntValue
- Replace all
object
entries by IntObject
- Run the code or trace it line by line in Debug mode
Use Case 1
Simple Boxing and unboxing can be implementing by the following code:
int i = 9;
object o = i;
int i2 = (int)o;
The code can be modeled as:
IntValue i = 9;
IntObject o = i;
IntValue i2 = (IntValue)o;
Output:
Boxing 9
UnBoxing 9
Use Case 2
Boxing and unboxing when use containers can be implemented by the following code:
List<object> l = new List<object>();
for (int ii = 0; ii < 10; ii++)
l.Add(ii);
int sum = 0;
for (int ii = 0; ii < l.Count; ii++)
sum += (int)l[ii];
Console.WriteLine(sum);
The code can be modeled as:
List<IntObject> l = new List<IntObject>();
for (IntValue ii = 0; ii < 10; ii++)
l.Add(ii);
int sum = 0;
for (IntValue ii = 0; ii < l.Count; ii++)
sum += (IntValue)l[ii];
Console.WriteLine(sum);
Output:
Boxing 0
Boxing 1
Boxing 2
Boxing 3
Boxing 4
Boxing 5
Boxing 6
Boxing 7
Boxing 8
Boxing 9
UnBoxing 0
UnBoxing 1
UnBoxing 2
UnBoxing 3
UnBoxing 4
UnBoxing 5
UnBoxing 6
UnBoxing 7
UnBoxing 8
UnBoxing 9
45
Use Case 3
Studying types of original and boxed values can be implementing by the following code:
int i3 = 37;
object o3 = i3;
Console.WriteLine(i3.GetType() + " " + o3.GetType());
The code can be modeled as:
IntValue i3 = 37;
IntObject o3 = i3;
Console.WriteLine(i3.GetType() + " " + o3.GetType());
Output:
Boxing 37
BoxingModel.IntValue BoxingModel.IntValue
Use Case 4
Studying how taking interface
from original type can be implementing by the following code:
int i4a = 495;
IComparable ic4 = (IComparable)i4a;
int i4b = 384;
Console.WriteLine("495 compared to {0} gives {1}", i4b, ic4.CompareTo(i4b));
The code can be modeled as:
IntValue i4a = 495;
IComparable ic4 = (IntObject)i4a;
IntValue i4b = 384;
Console.WriteLine("495 compared to {0} gives {1}", i4b, ic4.CompareTo(i4b));
Output:
Boxing 495
495 compared to 384 gives 1
Conclusion
The model is not ideal, but the main idea is to introduce a new method of teaching hidden implicit processes by modelling by programming language constructions of higher abstraction. The model can be extended including explanation of additional features and use cases.