Understanding Values and References in Microsoft Visual C#
- 11/18/2015
- Copying value type variables and classes
- Understanding null values and nullable types
- Using ref and out parameters
- How computer memory is organized
- The System.Object class
- Boxing
- Unboxing
- Casting data safely
- Summary
- Quick reference
Understanding null values and nullable types
When you declare a variable, it is always a good idea to initialize it. With value types, it is common to see code such as this:
int i = 0; double d = 0.0;
Remember that to initialize a reference variable such as a class, you can create a new instance of the class and assign the reference variable to the new object, like this:
Circle c = new Circle(42);
This is all very well, but what if you don’t actually want to create a new object? Perhaps the purpose of the variable is simply to store a reference to an existing object at some later point in your program. In the following code example, the Circle variable copy is initialized, but later it is assigned a reference to another instance of the Circle class:
Circle c = new Circle(42); Circle copy = new Circle(99); // Some random value, for initializing copy ... copy = c; // copy and c refer to the same object
After assigning c to copy, what happens to the original Circle object with a radius of 99 that you used to initialize copy? Nothing refers to it anymore. In this situation, the runtime can reclaim the memory by performing an operation known as garbage collection, which you will learn more about in Chapter 14, “Using garbage collection and resource management.” The important thing to understand for now is that garbage collection is a potentially time-consuming operation; you should not create objects that are never used because doing so is a waste of time and resources.
You could argue that if a variable is going to be assigned a reference to another object at some point in a program, there is no point to initializing it. But this is poor programming practice, which can lead to problems in your code. For example, you will inevitably find yourself in the situation in which you want to refer a variable to an object only if that variable does not already contain a reference, as shown in the following code example:
Circle c = new Circle(42); Circle copy; // Uninitialized !!! ... if (copy == // only assign to copy if it is uninitialized, but what goes here?) { copy = c; // copy and c refer to the same object ... }
The purpose of the if statement is to test the copy variable to see whether it is initialized, but to which value should you compare this variable? The answer is to use a special value called null.
In C#, you can assign the null value to any reference variable. The null value simply means that the variable does not refer to an object in memory. You can use it like this:
Circle c = new Circle(42); Circle copy = null; // Initialized ... if (copy == null) { copy = c; // copy and c refer to the same object ... }
Using nullable types
The null value is useful for initializing reference types. Sometimes, you need an equivalent value for value types, but null is itself a reference, so you cannot assign it to a value type. The following statement is therefore illegal in C#:
int i = null; // illegal
However, C# defines a modifier that you can use to declare that a variable is a nullable value type. A nullable value type behaves in a similar manner to the original value type, but you can assign the null value to it. You use the question mark (?) to indicate that a value type is nullable, like this:
int? i = null; // legal
You can ascertain whether a nullable variable contains null by testing it in the same way as you test a reference type.
if (i == null) ...
You can assign an expression of the appropriate value type directly to a nullable variable. The following examples are all legal:
int? i = null; int j = 99; i = 100; // Copy a value type constant to a nullable type i = j; // Copy a value type variable to a nullable type
You should note that the converse is not true. You cannot assign a nullable variable to an ordinary value type variable. So, given the definitions of variables i and j from the preceding example, the following statement is not allowed:
j = i; // Illegal
This makes sense when you consider that the variable i might contain null, and j is a value type that cannot contain null. This also means that you cannot use a nullable variable as a parameter to a method that expects an ordinary value type. If you recall, the Pass.Value method from the preceding exercise expects an ordinary int parameter, so the following method call will not compile:
int? i = 99; Pass.Value(i); // Compiler error
Understanding the properties of nullable types
A nullable type exposes a pair of properties that you can use to determine whether the type actually has a nonnull value and what this value is. The HasValue property indicates whether a nullable type contains a value or is null. You can retrieve the value of a nonnull nullable type by reading the Value property, like this:
int? i = null; ... if (!i.HasValue) { // If i is null, then assign it the value 99 i = 99; } else { // If i is not null, then display its value Console.WriteLine(i.Value); }
Chapter 4, “Using decision statements,” instructs that the NOT operator (!) negates a Boolean value. This code fragment tests the nullable variable i, and if it does not have a value (it is null), it assigns it the value 99; otherwise, it displays the value of the variable. In this example, using the HasValue property does not provide any benefit over testing for a null value directly. Additionally, reading the Value property is a long-winded way of reading the contents of the variable. However, these apparent shortcomings are caused by the fact that int? is a very simple nullable type. You can create more complex value types and use them to declare nullable variables where the advantages of using the HasValue and Value properties become more apparent. You will see some examples in Chapter 9, “Creating value types with enumerations and structures.”