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.
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.
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 if
…else
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.
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.
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.
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.