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 (i.e., the location in the computer's memory where the object itself is stored). 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 application 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 of a reference type, the method makes a copy of the reference, not a copy of the actual object that is referenced. The local copy of the reference also refers to the original object in memory, which means that changes to the object in the called method affect the original object in memory.
In Section 7.14, you learned that C# allows variables to be passed by reference with keyword ref. You can also 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 in memory. This is a subtle capability, which if misused, can lead to 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 different object, or even with null. Such behavior can lead to unpredictable effects, which can be disastrous in mission-critical applications. The application in Fig. 8.14 demonstrates the subtle difference between passing a reference by value and passing a reference by reference with keyword ref.
Figure 8.14. Passing an array reference by value and by reference.
1 // Fig. 8.14: ArrayReferenceTest.cs 2 // Testing the effects of passing array references 3 // by value and by reference. 4 using System; 5 6 public class ArrayReferenceTest 7 { 8 public 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( 17 "Test passing firstArray reference by value" ); 18 19 Console.Write( " Contents of firstArray " + 20 "before calling FirstDouble: " ); 21 22 // print contents of firstArray 23 for ( int i = 0; i < firstArray.Length; i++ ) 24 Console.Write( "{0} ", firstArray[ i ] ); 25 26 // pass variable firstArray by value to FirstDouble 27 FirstDouble( firstArray ); 28 29 Console.Write( " Contents of firstArray after " + 30 "calling FirstDouble " ); 31 32 // print contents of firstArray 33 for ( int i = 0; i < firstArray.Length; i++ ) 34 Console.Write( "{0} ", firstArray[ i ] ); 35 36 // test whether reference was changed by FirstDouble 37 if ( firstArray == firstArrayCopy ) 38 Console.WriteLine( 39 " The references refer to the same array" ); 40 else 41 Console.WriteLine( 42 " The references refer to different arrays" ); 43 44 // create and initialize secondArray 45 int[] secondArray = { 1, 2, 3 }; 46 47 // copy the reference in variable secondArray 48 int[] secondArrayCopy = secondArray; 49 50 Console.WriteLine( " Test passing secondArray " + 51 "reference by reference" ); 52 53 Console.Write( " Contents of secondArray " + 54 "before calling SecondDouble: " ); 55 56 // print contents of secondArray before method call 57 for ( int i = 0; i < secondArray.Length; i++ ) 58 Console.Write( "{0} ", secondArray[ i ] ); 59 60 // pass variable secondArray by reference to SecondDouble 61 SecondDouble( ref secondArray ); 62 63 Console.Write( " Contents of secondArray " + 64 "after calling SecondDouble: " ); 65 66 // print contents of secondArray after method call 67 for ( int i = 0; i < secondArray.Length; i++ ) 68 Console.Write( "{0} ", secondArray[ i ] ); 69 70 // test whether reference was changed by SecondDouble 71 if ( secondArray == secondArrayCopy ) 72 Console.WriteLine( 73 " The references refer to the same array" ); 74 else 75 Console.WriteLine( 76 " The references refer to different arrays" ); 77 } // end method Main 78 79 // modify elements of array and attempt to modify reference 80 public static void FirstDouble( int[] array ) 81 { 82 // double each element's value 83 for ( int i = 0; i < array.Length; i++ ) 84 array[ i ] *= 2; 85 86 // create new object and assign its reference to array 87 array = new int[] { 11, 12, 13 }; 88 } // end method FirstDouble 89 90 // modify elements of array and change reference array 91 // to refer to a new array 92 public static void SecondDouble( ref int[] array ) 93 { 94 // double each element's value 95 for ( int i = 0; i < array.Length; i++ ) 96 array[ i ] *= 2; 97 98 // create new object and assign its reference to array 99 array = new int[] { 11, 12, 13 }; 100 } // end method SecondDouble 101 } // end class ArrayReferenceTest
|
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 on line 14 copies the reference stored in firstArray to variable firstArrayCopy, causing these variables to reference the same array object in memory. We make the copy of the reference so that we can determine later whether reference firstArray gets overwritten. The for statement at lines 2324 prints the contents of firstArray before it is passed to method FirstDouble (line 27) so that we can verify that the array is passed by reference (i.e., the called method indeed changes the array's contents).
The for statement in method FirstDouble (lines 8384) multiplies the values of all the elements in the array by 2. Line 87 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 callerthis, of course, does not happen, because the reference was passed by value. After method FirstDouble executes, the for statement at lines 3334 prints the contents of firstArray, demonstrating that the values of the elements have been changed by the method (and confirming that in C# arrays are always passed by reference). The if...else statement at lines 3742 uses the == operator to compare references firstArray (which we just attempted to overwrite) and firstArrayCopy. The expression in line 37 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 11not the array created in method FirstDouble (line 87)so the original reference stored in firstArray was not modified.
Lines 4576 perform similar tests, using array variables secondArray and secondArrayCopy, and method SecondDouble (lines 92100). 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 99 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 objectin this case, an array created in SecondDouble. The if...else statement in lines 7176 confirms that secondArray and secondArrayCopy no longer refer to the same array.
|
|