8.13 (Optional) Passing Arrays by Value and by Reference

In C#, a variable that “stores” an object, such as an array, does not actually store the object itself. Instead, such a variable stores a reference to the object. The distinction between reference-type variables and value-type variables raises some subtle issues that you must understand to create secure, stable programs.

As you know, when an app passes an argument to a method, the called method receives a copy of that argument’s value. Changes to the local copy in the called method do not affect the original variable in the caller. If the argument is a reference type, the method makes a copy of the reference, not a copy of the actual object that’s referenced. The local copy of the reference also refers to the original object, which means that changes to the object in the called method affect the original object.

Performance Tip 8.1

Passing references to arrays and other objects makes sense for performance reasons. If arrays were passed by value, a copy of each element would be passed. For large, frequently passed arrays, this would waste time and consume considerable storage for the copies of the arrays.

In Section 7.18, you learned that C# allows variables to be passed by reference with keyword ref. You also can use keyword ref to pass a reference-type variable by reference, which allows the called method to modify the original variable in the caller and make that variable refer to a different object. This is a capability, which, if misused, can lead to subtle problems. For instance, when a reference-type object like an array is passed with ref, the called method actually gains control over the reference itself, allowing the called method to replace the original reference in the caller with a reference to a different object, or even with null. Such behavior can lead to unpredictable effects, which can be disastrous in mission-critical apps.

Figure 8.25 demonstrates the subtle difference between passing a reference by value and passing a reference by reference with keyword ref. Lines 11 and 14 declare two integer array variables, firstArray and firstArrayCopy. Line 11 initializes firstArray with the values 1, 2 and 3. The assignment statement at line 14 copies the reference stored in firstArray to variable firstArrayCopy, causing these variables to reference the same array object. We make the copy of the reference so that we can determine later whether reference firstArray gets overwritten. The foreach statement at lines 21–24 displays the contents of firstArray before it’s passed to method FirstDouble (line 27) so that we can verify that the called method indeed changes the array’s contents.

Fig. 8.25 Testing the effects of passing an array reference by value and by reference.

Alternate View

  1   // Fig. 8.25: ArrayReferenceTest.cs
  2   // Testing the effects of passing array references
  3   // by value and by reference.
  4   using System;
  5
  6   class ArrayReferenceTest
  7   {
  8      static void Main(string[] args)
  9      {
 10         // create and initialize firstArray
 11         int[] firstArray = {1, 2, 3};
 12
 13         // copy the reference in variable firstArray
 14         int[] firstArrayCopy = firstArray;
 15
 16         Console.WriteLine("Test passing firstArray reference by value");
 17         Console.Write(
 18            "Contents of firstArray before calling FirstDouble:
	");
 19
 20         // display contents of firstArray
 21         foreach (var element in firstArray)
 22         {
 23            Console.Write($"{element} ");
 24         }
 25
 26         // pass variable firstArray by value to FirstDouble
 27         FirstDouble(firstArray);                           
 28
 29         Console.Write(
 30            "
Contents of firstArray after calling FirstDouble
	");
 31
 32         // display contents of firstArray
 33         foreach (var element in firstArray)
 34         {
 35            Console.Write($"{element} ");
 36         }
 37
 38         // test whether reference was changed by FirstDouble
 39         if (firstArray == firstArrayCopy)
 40         {
 41            Console.WriteLine("

The references refer to the same array");
 42         }
 43         else
 44         {
 45            Console.WriteLine(
 46               "

The references refer to different arrays");
 47         }
 48
 49         // create and initialize secondArray
 50         int[] secondArray = {1, 2, 3};
 51
 52         // copy the reference in variable secondArray
 53         int[] secondArrayCopy = secondArray;
 54
 55         Console.WriteLine(
 56             "
Test passing secondArray reference by reference");
 57         Console.Write(
 58             "Contents of secondArray before calling SecondDouble:
	");
 59
 60         // display contents of secondArray before method call
 61         foreach (var element in secondArray)
 62         {
 63            Console.Write($"{element} ");
 64         }
 65
 66         // pass variable secondArray by reference to SecondDouble
 67         SecondDouble(ref secondArray);
 68
 69         Console.Write(
 70            "
Contents of secondArray after calling SecondDouble:
	");
 71
 72         // display contents of secondArray after method call
 73         foreach (var element in secondArray)
 74         {
 75            Console.Write($"{element} ");
 76         }
 77
 78         // test whether reference was changed by SecondDouble
 79         if (secondArray == secondArrayCopy)
 80         {
 81            Console.WriteLine("

The references refer to the same array");
 82         }
 83         else
 84         {
 85            Console.WriteLine(
 86               "

The references refer to different arrays");
 87         }
 88      }
 89
 90      // modify elements of array and attempt to modify reference
 91      static void FirstDouble(int[] array)
 92      {
 93         // double each element's value
 94         for (var i = 0; i < array.Length; ++i)
 95         {
 96            array[i] *= 2;
 97         }
 98
 99         // create new object and assign its reference to array
100         array = new int[] {11, 12, 13};                       
101      }
102
103      // modify elements of array and change reference array
104      // to refer to a new array
105      static void SecondDouble(ref int[] array)
106      {
107         // double each element's value
108         for (var i = 0; i < array.Length; ++i)
109         {
110            array[i] *= 2;
111         }
112
113         // create new object and assign its reference to array
114         array = new int[] {11, 12, 13};                       
115      }
116   }

Test passing firstArray reference by value
Contents of firstArray before calling FirstDouble:
         1 2 3
Contents of firstArray after calling FirstDouble
         2 4 6

The references refer to the same array

Test passing secondArray reference by reference
Contents of secondArray before calling SecondDouble:
         1 2 3
Contents of secondArray after calling SecondDouble:
         11 12 13

The references refer to different arrays

Method FirstDouble

The for statement in method FirstDouble (lines 94–97) multiplies the values of all the elements in the array by 2. Line 100 creates a new array containing the values 11, 12 and 13, and assigns the array’s reference to parameter array in an attempt to overwrite reference firstArray in the caller—this, of course, does not happen, because the reference was passed by value. After method FirstDouble executes, the foreach statement at lines 33– 36 displays firstArray’s contents, demonstrating that the values of the elements have been changed by the method. The ifelse statement at lines 39–47 uses the == operator to compare references firstArray (which we just attempted to overwrite) and firstArrayCopy. The expression in line 39 evaluates to true if the operands of operator == reference the same object. In this case, the object represented by firstArray is the array created in line 11—not the array created in method FirstDouble (line 100)—so the original reference stored in firstArray was not modified.

Method SecondDouble

Lines 50–87 perform similar tests, using array variables secondArray and second-ArrayCopy, and method SecondDouble (lines 105–115). Method SecondDouble performs the same operations as FirstDouble, but receives its array argument using keyword ref. In this case, the reference stored in secondArray after the method call is a reference to the array created in line 114 of SecondDouble, demonstrating that a variable passed with keyword ref can be modified by the called method so that the variable in the caller actually points to a different object—in this case, an array created in SecondDouble. The if...else statement in lines 79–87 confirms that secondArray and secondArrayCopy no longer refer to the same array.

Software Engineering Observation 8.3

When a method receives a reference-type parameter by value, a copy of the object’s reference is passed. This prevents a method from overwriting references passed to that method. In the vast majority of cases, protecting the caller’s reference from modification is the desired behavior. If you encounter a situation where you truly want the called procedure to modify the caller’s reference, pass the reference-type parameter using keyword ref—but, again, such situations are rare.

 

Software Engineering Observation 8.4

In C#, references to objects (including arrays) are passed to called methods. A called method—receiving a reference to an object in a caller—can interact with, and possibly change, the caller’s object.

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

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