Thursday, October 22, 2009

Understanding Value Types and Reference Types

Note The following discussion of value types and reference types assumes that you have a background in
object-oriented programming.We will examine a number of topics that assume you have a background in objectoriented
programming. If this is not the case, you may wish to reread this section once you have completed

Unlike arrays, strings, or enumerations, C# structures do not have an identically named representation
in the .NET library (that is, there is no System.Structure class), but are implicitly derived from
System.ValueType. Simply put, the role of System.ValueType is to ensure that the derived type (e.g.,
any structure) is allocated on the stack rather than the garbage collected heap.
Functionally, the only purpose of System.ValueType is to “override” the virtual methods defined
by System.Object to use value-based, versus reference-based, semantics. As you may know, overriding
is the process of changing the implementation of a virtual (or possibly abstract) method defined
within a base class. The base class of ValueType is System.Object. In fact, the instance methods
defined by System.ValueType are identical to those of System.Object:
// Structures and enumerations extend System.ValueType.
public abstract class ValueType : object
{
public virtual bool Equals(object obj);
public virtual int GetHashCode();
public Type GetType();
public virtual string ToString();
}
Given the fact that value types are using value-based semantics, the lifetime of a structure
(which includes all numerical data types [int, float, etc.], as well as any enum or custom structure)
is very predictable. When a structure variable falls out of the defining scope, it is removed from
memory immediately:
// Local structures are popped off
// the stack when a method returns.
static void LocalValueTypes()
{
// Recall! "int" is really a System.Int32 structure.
int i = 0;
// Recall! Point is a structure type.
Point p = new Point();
} // "i" and "p" popped off the stack here!
Value Types, References Types, and the Assignment Operator
When you assign one value type to another, a member-by-member copy of the field data is
achieved. In the case of a simple data type such as System.Int32, the only member to copy is the
numerical value. However, in the case of our Point, the X and Y values are copied into the new
structure variable. To illustrate, create a new Console Application project named
ValueAndReferenceTypes and copy your previous Point definition into your new namespace.
Now, add the following method to your Program type:

// Assigning two intrinsic value types results in
// two independent variables on the stack.
static void ValueTypeAssignment()
{
Console.WriteLine("Assigning value types\n");
Point p1 = new Point(10, 10);
Point p2 = p1;
// Print both points.
p1.Display();
p2.Display();
// Change p1.X and print again. p2.X is not changed.
p1.X = 100;
Console.WriteLine("\n=> Changed p1.X\n");
p1.Display();
p2.Display();
}
Here you have created a variable of type Point (named p1) that is then assigned to another
Point (p2). Because Point is a value type, you have two copies of the MyPoint type on the stack, each
of which can be independently manipulated. Therefore, when you change the value of p1.X, the
value of p2.X is unaffected. Figure 4-10 shows the output once this method is called from Main().
Figure 4-10. Assignment of value types results in a verbatim copy of each field.
In stark contrast to value types, when you apply the assignment operator to reference types
(meaning all class instances), you are redirecting what the reference variable points to in memory.
To illustrate, create a new class type named PointRef that has the exact same members as the Point
structures, beyond renaming the constructor to match the class name:
// Classes are always reference types.
class PointRef
{
// Same members as the Point structure.
// Be sure to change your constructor name to PointRef!
public PointRef(int XPos, int YPos)
{
X = XPos;

Y = YPos;
}
}
Now, make use of your PointRef type within the following new method (note the code is identical
to the ValueTypeAssignment() method). Assuming you have called this new method within
Main(), your output should look like that in Figure 4-11.
static void ReferenceTypeAssignment()
{
Console.WriteLine("Assigning reference types\n");
PointRef p1 = new PointRef(10, 10);
PointRef p2 = p1;
// Print both point refs.
p1.Display();
p2.Display();
// Change p1.X and print again.
p1.X = 100;
Console.WriteLine("\n=> Changed p1.X\n");
p1.Display();
p2.Display();
}
Figure 4-11. Assignment of reference types copies the reference.
In this case, you have two references pointing to the same object on the managed heap. Therefore,
when you change the value of X using the p2 reference, p1.X reports the same value.
Value Types Containing Reference Types
Now that you have a better feeling for the core differences between value types and reference types,
let’s examine a more complex example. Assume you have the following reference (class) type that
maintains an informational string that can be set using a custom constructor:
class ShapeInfo
{
public string infoString;
public ShapeInfo(string info)
{ infoString = info; }
}

Now assume that you want to contain a variable of this class type within a value type named
Rectangle. To allow the caller to set the value of the inner ShapeInfo member variable, you also provide
a custom constructor. Here is the complete definition of the Rectangle type:
struct Rectangle
{
// The Rectangle structure contains a reference type member.
public ShapeInfo rectInfo;
public int rectTop, rectLeft, rectBottom, rectRight;
public Rectangle(string info, int top, int left, int bottom, int right)
{
rectInfo = new ShapeInfo(info);
rectTop = top; rectBottom = bottom;
rectLeft = left; rectRight = right;
}
public void Display()
{
Console.WriteLine("String = {0}, Top = {1}, Bottom = {2}," +
"Left = {3}, Right = {4}",
rectInfo.infoString, rectTop, rectBottom, rectLeft, rectRight);
}
}
At this point, you have contained a reference type within a value type. The million-dollar question
now becomes, What happens if you assign one Rectangle variable to another? Given what you
already know about value types, you would be correct in assuming that the integer data (which is
indeed a structure) should be an independent entity for each Rectangle variable. But what about
the internal reference type? Will the object’s state be fully copied, or will the reference to that object
be copied? To answer this question, define the following method and invoke it from Main(). Check

static void ValueTypeContainingRefType()
{
// Create the first Rectangle.
Console.WriteLine("-> Creating r1");
Rectangle r1 = new Rectangle("First Rect", 10, 10, 50, 50);
// Now assign a new Rectangle to r1.
Console.WriteLine("-> Assigning r2 to r1");
Rectangle r2 = r1;
// Change some values of r2.
Console.WriteLine("-> Changing values of r2");
r2.rectInfo.infoString = "This is new info!";
r2.rectBottom = 4444;
// Print values of both rectangles.
r1.Display();
r2.Display();
}

As you can see, when you change the value of the informational string using the r2 reference,
the r1 reference displays the same value. By default, when a value type contains other reference
types, assignment results in a copy of the references. In this way, you have two independent structures,
each of which contains a reference pointing to the same object in memory (i.e., a “shallow
copy”). When you want to perform a “deep copy,” where the state of internal references is fully
copied into a new object, one approach is to implement the ICloneable interface (as you will do in
Chapter 9).
nSource Code The ValueAndReferenceTypes project is located under the Chapter 4 subdirectory.
Passing Reference Types by Value
Reference types or value types can obviously be passed as parameters to type members. However,
passing a reference type (e.g., a class) by reference is quite different from passing it by value. To
understand the distinction, assume you have a simple Person class defined in a new Console Application
project named RefTypeValTypeParams, defined as follows:
class Person
{
public string personName;
public int personAge;
// Constructors.
public Person(string name, int age)
{
personName = name;
personAge = age;
}
public Person(){}
public void Display()
{
Console.WriteLine("Name: {0}, Age: {1}", personName, personAge);
}
}
Now, what if you create a method that allows the caller to send in the Person type by value
(note the lack of parameter modifiers):

static void SendAPersonByValue(Person p)
{
// Change the age of "p"?
p.personAge = 99;
// Will the caller see this reassignment?
p = new Person("Nikki", 99);
}
Notice how the SendAPersonByValue() method attempts to reassign the incoming Person
reference to a new object as well as change some state data. Now let’s test this method using the
following Main() method:
static void Main(string[] args)
{
// Passing ref-types by value.
Console.WriteLine("***** Passing Person object by value *****");
Person fred = new Person("Fred", 12);
Console.WriteLine("\nBefore by value call, Person is:");
fred.Display();
SendAPersonByValue(fred);
Console.WriteLine("\nAfter by value call, Person is:");
fred. Display();
Console.ReadLine();
}
Figure 4-13. Passing reference types by value locks the reference in place.
As you can see, the value of personAge has been modified. This behavior seems to fly in the face
of what it means to pass a parameter “by value.” Given that you were able to change the state of the
incoming Person, what was copied? The answer: a copy of the reference to the caller’s object. Therefore,
as the SendAPersonByValue() method is pointing to the same object as the caller, it is possible
to alter the object’s state data. What is not possible is to reassign what the reference is pointing to.
Passing Reference Types by Reference
Now assume you have a SendAPersonByReference() method, which passes a reference type by reference
(note the ref parameter modifier):
static void SendAPersonByReference(ref Person p)
{
// Change some data of "p".
p.personAge = 555;

// "p" is now pointing to a new object on the heap!
p = new Person("Nikki", 999);
}
As you might expect, this allows complete flexibility of how the callee is able to manipulate the
incoming parameter. Not only can the callee change the state of the object, but if it so chooses, it
may also reassign the reference to a new Person type. Now ponder the following updated Main()
method and check Figure 4-14 for output:
static void Main(string[] args)
{
// Passing ref-types by ref.
Console.WriteLine("\n***** Passing Person object by reference *****");
Person mel = new Person("Mel", 23);
Console.WriteLine("Before by ref call, Person is:");
mel.Display();
SendAPersonByReference(ref mel);
Console.WriteLine("After by ref call, Person is:");
mel.Display();
Console.ReadLine();
}
Figure 4-14. Passing reference types by reference allows the reference to be redirected.
As you can see, an object named Mel returns after the call as a type named Nikki, as the
method was able to change what the incoming reference pointed to in memory. The golden rule to
keep in mind when passing reference types:
• If a reference type is passed by reference, the callee may change the values of the object’s
state data as well as the object it is referencing.
• If a reference type is passed by value, the callee may change the values of the object’s state
data but not the object it is referencing.
nSource Code The RefTypeValTypeParams project is located under the Chapter 4 subdirectory.
Value and Reference Types: Final Details
To wrap up this topic, consider the information in Table 4-3, which summarizes the core distinctions
between value types and reference types.

How is a variable represented? Value type variables Reference type variables are
are local copies. pointing to the memory
occupied by the allocated
instance.
What is the base type? Must derive from Can derive from any other
System.ValueType. type (except System.
ValueType), as long as that
type is not “sealed” (more
details on this in Chapter 6).
Can this type function as a No. Value types are always Yes. If the type is not sealed,
base to other types? sealed and cannot be it may function as a base to
extended. other types.
What is the default parameter Variables are passed by value Variables are passed by
passing behavior? (i.e., a copy of the variable is reference (i.e., the address
passed into the called function). of the variable is passed into
the called function).
Can this type override No. Value types are never placed Yes, indirectly (more details
System.Object.Finalize()? onto the heap and therefore do on this in Chapter 8).
not need to be finalized.
Can I define constructors Yes, but the default constructor But of course!
for this type? is reserved (i.e., your custom
constructors must all have
arguments).
When do variables of this When they fall out of the When the object is garbage
type die? defining scope. collected.
Despite their differences, value types and reference types both have the ability to implement
interfaces and may support any number of fields, methods, overloaded operators, constants, properties,
and events.

No comments:

Post a Comment