Early in this book, you learned about the difference between value types and reference types. A value type is a type in which the corresponding data is always contained within the variable of that type and is passed as method parameters on the stack. A reference type corresponds to what developers of unmanaged code might refer to as a pointer. A variable that represents an instance of a reference type doesn't contain the raw data. Rather, it contains information about where to find the raw data within the managed heap.
Every time a method is invoked, the stack is built and then deconstructed in order to get at the parameter data. As a result of this, as a general rule, developers should avoid creating value types that are very large, to keep stack operations running as quickly as possible.
One aspect of value and reference types that hasn't been mentioned yet is the concept of boxing and unboxing. When a value type needs to be treated as though it were a reference type, the value type is Boxed.
Boxing is the process by which the data for a value type is placed on the managed heap and then a pointer to that data is used as the contents of the newly created reference type instance. For example, the following lines of code illustrate the use of boxing:
object o = 21; // boxing operation occurs, "21" becomes reference int z = 42; // no boxing here, all value type operations object y = z; // boxing operation occurs, "z" becomes reference
The process of boxing a variable takes time because new space needs to be allocated on the managed heap for the variable that would otherwise remain in the relatively fast stack. Then, when the boxed variable is no longer needed, that same space needs to be reclaimed by the garbage collector, further reducing performance.
Conversely, when a boxed value type (value contained in a reference) needs to again be treated as a value type, the value needs to be retrieved from the managed heap and placed on the stack. This process is called unboxing and also incurs an overhead cost.
If you write your code in such a way that you avoid boxing and unboxing, especially within loops, you will find that your code performs faster. Although the occasional use of boxing or unboxing won't drag your application down, large loops that use boxing inside frequently invoked methods in large, multiuser applications can definitely have a negative impact on performance.
The code in Listing 16.1 uses the Ticks property of a DateTime instance to show the difference in execution time for a 300,000-iteration loop with and without boxing. On the author's machine, the boxing loop took 156,250 ticks, whereas the loop that did not use boxing took place in less than 1 tick. Although that still is a small amount of time on its own, when you attempt to scale that out to a method that is being invoked thousands of times per minute by thousands of users on a large application, the difference in time will become quite noticeable.
Listing 16.1. Timing Boxing Operations
The general rule of thumb is to treat value types as value types unless you absolutely cannot avoid it. In previous versions of the .NET Framework, there were many scenarios where an int would have to be passed as an object in order for a class to be able to work with multiple data types. You can use generics in these situations so that, at runtime, the value type will be treated as a value type and will not incur the boxing overhead.