Memory Allocation: The Stack Versus the Heap

Objects created within methods are called local variables, as we discussed earlier. They are local to the method, as opposed to belonging to the whole object, as member variables are. The object is created within the method, used within the method, and then destroyed sometime after the method ends. Local objects are not part of the object’s state—they are temporary value holders, useful only within the particular method.

Local variables of intrinsic types such as int are created on a portion of memory known as the stack. The stack is allocated and de-allocated as methods are invoked. When you start a method, all its local variables are created on the stack. When the method ends, local variables are destroyed.

These variables are referred to as local because they exist (and are visible) only during the lifetime of the method. They are said to have local scope. When the method ends, the variable goes out of scope and is destroyed.

C# divides the world of types into value types and reference types. Value types are created on the stack. All the intrinsic types (int, long) are value types (as are structs, discussed later in this chapter), and thus are created on the stack.

Objects, on the other hand, are reference types. Reference types are created on an undifferentiated block of memory known as the heap. When you declare an instance of a reference type, what you are actually declaring is a reference, which is a variable that refers to another object. The reference acts like an alias for the object.

That is, when you write:

Dog milo = new Dog( );

the new operator creates a Dog object on the heap and returns a reference to it. That reference is assigned to milo. Thus, milo is a reference object that refers to a Dog object on the heap. It is common to say that milo is a reference to a Dog, or even that milo is a "Dog object,” but technically that is incorrect. milo is actually a reference that refers to an (unnamed) Dog object on the heap.

The reference milo acts as an alias for that unnamed object. For all practical purposes, however, you can treat milo as though it were the Dog object itself. In other words, it’s fine to go on referring to milo as a Dog object. He won’t mind.

The implication of using references is that you can have more than one reference to the same object. To see this difference between creating value types and reference types, examine Example 7-7. A complete analysis follows the output.

Example 7-7. Value types are created on the stack, and reference types are created on the heap

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example_7_7_ _ _ _Value_and_Reference_Types
{
   public class Dog
   {
      public int weight;
   }

   class Tester
   {
      public void Run( )
      {
         // create an integer
         int firstInt = 5;

         // create a second integer
         int secondInt = firstInt;

         // display the two integers
         Console.WriteLine( "firstInt: {0} secondInt: {1}",
                            firstInt, secondInt );

         // modify the second integer
         secondInt = 7;

         // display the two integers
         Console.WriteLine( "firstInt: {0} secondInt: {1}",
                            firstInt, secondInt );

         // create a dog
         Dog milo = new Dog( );

         // assign a value to weight
         milo.weight = 5;

         // create a second reference to the dog
         Dog fido = milo;

         // display their values
         Console.WriteLine( "milo: {0}, fido: {1}",
                            milo.weight, fido.weight );

         // assign a new weight to the second reference
         fido.weight = 7;

         // display the two values
         Console.WriteLine( "milo: {0}, fido: {1}",
                            milo.weight, fido.weight );
      }

      static void Main( )
      {
         Tester t = new Tester( );
         t.Run( );
      }
   }
}

The output looks like this:

firstInt: 5 secondInt: 5
firstInt: 5 secondInt: 7
Milo: 5, fido: 5
Milo: 7, fido: 7

The program begins by creating an integer, firstInt, and initializing it with the value 5. The second integer, secondInt, is then created and initialized with the value in firstInt. Their values are displayed as output:

firstInt: 5 secondInt: 5

These values are identical. Because int is a value type, a copy of the firstInt value is made and assigned to secondInt; secondInt is an independent second variable, as illustrated in Figure 7-2.

secondInt is a copy of firstInt.

Figure 7-2. secondInt is a copy of firstInt.

Then the program assigns a new value to secondInt:

secondInt = 7;

Because these variables are value types, independent of one another, the first variable is unaffected. Only the copy is changed, as illustrated in Figure 7-3.

Only the copy is changed.

Figure 7-3. Only the copy is changed.

When the values are displayed, they are different:

firstInt: 5 secondInt: 7

Your next step is to create a simple Dog class with only one member variable, called weight. Note that this field is given an access modifier of public, which specifies that any method of any class can access this field. (Generally, you will not make member variables public. The weight field was made public to simplify this example.)

You instantiate a Dog object and save a reference to that dog in the reference milo:

Dog milo = new Dog( );

You assign the value 5 to milo’s weight field:

milo.weight = 5;

You commonly say that you’ve set milo’s weight to 5, but actually you’ve set the weight field of the unnamed object on the heap to which milo refers, as shown in Figure 7-4.

milo is a reference to an unnamed Dog object.

Figure 7-4. milo is a reference to an unnamed Dog object.

Next, you create a second reference to Dog and initialize it by setting it equal to milo. This creates a new reference to the same object on the heap.

Dog fido = milo;

Notice that this is syntactically similar to creating a second int variable and initializing it with an existing int, as you did before:

int secondInt = firstInt;
Dog fido = milo;

The difference is that Dog is a reference type, so fido is not a copy of milo—it is a second reference to the same object to which milo refers. That is, you now have an object on the heap with two references to it, as illustrated in Figure 7-5.

When you change the weight of that object through the fido reference:

fido.weight = 7;

you change the weight of the same object to which milo refers. The output reflects this:

Milo: 7, fido: 7

It isn’t that fido is changing milo; it is that by changing the (unnamed) object on the heap to which fido refers you simultaneously change the value of milo because they refer to the same unnamed object.

Tip

If you had used the keyword new when creating fido, you’d have created a new instance of Dog on the heap, and fido and milo would not point to the same Dog object.

fido is a second reference to the Dog object.

Figure 7-5. fido is a second reference to the Dog object.

If you need a class that acts as a value object, you can create a struct (see the "Structs” sidebar). The use of structs is so unusual that we do not cover them (beyond the sidebar) for the rest of this book. You should know what they are, but you’ll probably never need to use one.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset