Previous Page
Next Page

Copying int Variables and Classes

All primitive types such as int are called value types. When you declare an int variable, the compiler generates code that allocates a block of memory big enough to hold an integer. A statement that assigns a value (such as 42) to the int causes the value to be copied to this block of memory.

Class types, such as Circle (described in Chapter 7), are handled differently. When you declare a Circle variable, the compiler does not generate code that allocates a block of memory big enough to hold a Circle; all it does is allot a small piece of memory that can potentially hold the address of (or a reference to) another block of memory containing a Circle. The memory for the Circle object itself is only allocated when the new keyword is used to create the object.

This demonstrates that value types are so called because they hold values directly. Reference types (such as classes) hold references to blocks of memory.

NOTE
If you are a C or C++ programmer you might be tempted to think of reference types simply as pointers. While reference types in C# exhibit many similarities to pointers, they provide far more functionality. For example, in a C or C++ application it is possible to make a pointer reference almost any block of memory regardless of the type of data the block is holding. Sometimes this is useful, but more often than not it is the cause of many insidious programming errors. In C#, all references are strongly typed; you cannot declare a reference variable that refers to one type (such as Circle), and then use the variable to access a block of memory holding a different type. There are other differences as well, concerning the way in which the common language runtime manages and reclaims memory. These features are discussed in Chapter 13, “Using Garbage Collection and Resource Management.”

Because of the different ways that they hold data, value types are sometimes called direct types, and reference types are sometimes called indirect types. You need to fully understand the difference between value types and reference types.

Consider the situation where you declare a variable named i as an int and assign it the value 42. If you declare another variable copyi as an int, and initialize or assign copyi to i, copyi will hold the same value as i (42). However, the fact that copyi and i happen to hold the same value does not alter the fact that there are two copies of the value 42: one inside i and the other inside copyi. If you modify the value in i, the value in copyi does not change. Let's see this in code:

int i = 42;// declare and initialize i 
int copyi = i;// copyi contains a copy of the data in i 
i++;// incrementing i has no effect on copyi

The effect of declaring c as a Circle (the name of a class) is very different. When you declare c as a Circle, c can refer to a Circle object. If you declare refc as another Circle, it can also refer to a Circle object. If you choose to initialize or assign refc to c, refc will refer to the same Circle object that c does; there is only one Circle object, and refc and c both refer to it. Let's see this in code:

Circle c = new Circle(42); 
Circle refc = c;

The following graphic illustrates both examples:

Graphic

The difference explained above is very important. In particular, it means that the behavior of method parameters depends on whether they are value types or reference types. You'll explore this difference in the following exercise.

Use value parameters and reference parameters
  1. Start Microsoft Visual Studio 2005.

  2. Open the Parameters project, located in the \Microsoft Press\Visual CSharp Step by Step\Chapter 8\Parameters folder in your My Documents folder.

  3. Display the Pass.cs source file in the Code and Text Editor window. Locate the Pass class. Add a public static method called Value to the Pass class. This method should accept a single int parameter (a value type) called param and have a return type of void. The body of Value should simply assign 42 to param.

    The Pass class should look exactly like this:

    namespace Parameters 
    { 
        class Pass 
        { 
            public static void Value(int param) 
            { 
                param = 42; 
            } 
        } 
    }
  4. Display the Program.cs source file in the Code and Text Editor window, and then locate the Entrance method of the Program class.

    The Entrance method is called by the Main method when the program starts running. As explained in Chapter 7, the method call is wrapped in a try block and followed by a catch handler.

  5. Add four statements to the Entrance method to perform the following tasks:

    1. Declare a local int variable called i and initialize it to 0.

    2. Write the value of i to the console by using Console.WriteLine.

    3. Call Pass.Value, passing i as an argument.

    4. Write the value of i to the console again.

    The calls to Console.WriteLine before and after the call to Pass.Value allow you to see whether the call to Pass.Value actually modifies the value of i. The Entrance method should look exactly like this:

    static void Entrance() 
    { 
        int i = 0; 
        Console.WriteLine(i); 
        Pass.Value(i); 
        Console.WriteLine(i); 
    }
  6. On the Debug menu, click Start Without Debugging to build and run the program.

  7. Confirm that the value of 0 is written to the console window twice.

    The assignment inside Pass.Value was made by using a copy of the argument, and the original argument i is completely unaffected.

  8. Press the Enter key to close the application.

    You will now see what happens when you pass an int parameter that is wrapped inside a class.

  9. Display the WrappedInt.cs source file in the Code and Text Editor window. Add a public instance field called Number of type int to the WrappedInt class.

    The WrappedInt class should look exactly like this:

    namespace Parameters 
    { 
        class WrappedInt 
        { 
            public int Number; 
        } 
    }
  10. Display the Pass.cs source file in the Code and Text Editor window. Add a public static method called Reference to the Pass class. This method should accept a single WrappedInt parameter called param and have a return type of void. The body of the Reference method should assign 42 to param.Number.

    The Pass class should look exactly like this:

    namespace Parameters 
    { 
        class Pass 
        { 
            public static void Value(int param) 
            { 
                param = 42; 
            } 
     
            public static void Reference(WrappedInt param) 
            { 
                param.Number = 42; 
            } 
        } 
    }
  11. Display the Program.cs source file in the Code and Text Editor window. Add four more statements to the Entrance method to perform the following tasks:

    1. Declare a local WrappedInt variable called wi and initialize it to a new WrappedInt object by calling the default constructor.

    2. Write the value of wi.Number to the console.

    3. Call the Pass.Reference method, passing wi as an argument.

    4. Write the value of wi.Number to the console again.

    As before, the calls to Console.WriteLine allow you to see whether the call to Pass.Reference modifies the value of wi.Number. The Entrance method should now look exactly like this:

    static void Entrance() 
    { 
        int i = 0; 
        Console.WriteLine(i); 
        Pass.Value(i); 
        Console.WriteLine(i); 
     
        WrappedInt wi = new WrappedInt(); 
        Console.WriteLine(wi.Number); 
        Pass.Reference(wi); 
        Console.WriteLine(wi.Number); 
    }
  12. On the Debug menu, click Start Without Debugging to build and run the application.

    As before, the first two values written to the console window are 0 and 0, before and after the call to Pass.Value. For the next two values, which correspond to value wi.Number before and after Pass.Reference, confirm that the value of 0 and then the value of 42 are written to the console window.

  13. Press the Enter key to close the application.

In the previous exercise, the value of wi.Number is initialized to 0 by the compiler-generated code. The wi variable contains a reference to the newly created WrappedInt object (which contains an int). The wi variable is then copied as an argument to the Pass.Reference method. Because WrappedInt is a class (a reference type), wi and param both refer to the same WrappedInt object. Any changes made to the contents of the object through the param variable in the Pass.Reference method are visible by using the wi variable when the method completes. The following diagram illustrates what happens when a WrappedInt object is passed as an argument to the Pass.Reference method:

Graphic

Previous Page
Next Page