Passing Arguments: Pass-by-Value vs. Pass-by-Reference
Passing Arguments Pass by Value vs Pass by Reference
Two ways to pass arguments to functions in many programming languages are pass-by-value and pass-by-reference. When an argument is passed by value (the default in C#), a copy of the argument's value is made and passed to the called function. Changes to the copy do not affect the original variable's value in the caller. This prevents the accidental side effects that so greatly hinder the development of correct and reliable software systems. Each argument that has been passed in the programs in this chapter so far has been passed by value. When an argument is passed by reference, the caller gives the method the ability to access and modify the caller's original variable.
|
|
To pass an object by reference into a method, simply provide as an argument in the method call the variable that refers to the object. Then, in the method body, reference the object using the parameter name. The parameter refers to the original object in memory, so the called method can access the original object directly.
Previously, we discussed the difference between value types and reference types. One of the major differences between them is that value-type variables store values, so specifying a value-type variable in a method call passes a copy of that variable's value to the method. Reference-type variables store references to objects, so specifying a reference-type variable as an argument passes the method a copy of the actual reference that refers to the object. Even though the reference itself is passed by value, the method can still use the reference it receives to modify the original object in memory. Similarly, when returning information from a method via a return statement, the method returns a copy of the value stored in a value-type variable or a copy of the reference stored in a reference-type variable. When a reference is returned, the calling method can use that reference to interact with the referenced object. So, in effect, objects are always passed by reference.
What if you would like to pass a variable by reference so the called method can modify the variable's value? To do this, C# provides keywords ref and out. Applying the ref keyword to a parameter declaration allows you to pass a variable to a method by referencethe called method will be able to modify the original variable in the caller. The ref keyword is used for variables that already have been initialized in the calling method. Normally, when a method call contains an uninitialized variable as an argument, the compiler generates an error. Preceding a parameter with keyword out creates an output parameter. This indicates to the compiler that the argument will be passed into the called method by reference and that the called method will assign a value to the original variable in the caller. If the method does not assign a value to the output parameter in every possible path of execution, the compiler generates an error. This also prevents the compiler from generating an error message for an uninitialized variable that is passed as an argument to a method. A method can return only one value to its caller via a return statement, but can return many values by specifying multiple output parameters.
You can also pass a reference-type variable by reference, which allows you to modify the passed reference-type variable so that it references a new object. Passing a reference by reference is a tricky but powerful technique that we discuss in Section 8.8.
The application in Figs. 7.18 and 7.19 uses the ref and out keywords to manipulate integer values. Class ReferenceAndOutputParameters (Fig. 7.18) contains three methods that calculate the square of an integer. Method SquareRef (lines 3740) multiplies its parameter x by itself and assigns the new value to x. SquareRef's parameter x is declared as ref int, which indicates that the argument passed to this method must be an integer that is passed by reference. Because the argument is passed by reference, the assignment at line 39 modifies the original argument's value in the caller.
Figure 7.18. Reference, output and value parameters.
1 // Fig. 7.18: ReferenceAndOutputParameters.cs 2 // Reference, output and value parameters. 3 using System; 4 5 class ReferenceAndOutputParameters 6 { 7 // call methods with reference, output and value parameters 8 public void DemonstrateReferenceAndOutputParameters() 9 { 10 int y = 5; // initialize y to 5 11 int z; // declares z, but does not initialize it 12 13 // display original values of y and z 14 Console.WriteLine( "Original value of y: {0}", y ); 15 Console.WriteLine( "Original value of z: uninitialized " ); 16 17 // pass y and z by reference 18 SquareRef( ref y ); // must use keyword ref 19 SquareOut( out z ); // must use keyword out 20 21 // display values of y and z after they are modified by 22 // methods SquareRef and SquareOut, respectively 23 Console.WriteLine( "Value of y after SquareRef: {0}", y ); 24 Console.WriteLine( "Value of z after SquareOut: {0} ", z ); 25 26 // pass y and z by value 27 Square( y ); 28 Square( z ); 29 30 // display values of y and z after they are passed to method Square 31 // to demonstrate arguments passed by value are not modified 32 Console.WriteLine( "Value of y after Square: {0}", y ); 33 Console.WriteLine( "Value of z after Square: {0}", z ); 34 } // end method DemonstrateReferenceAndOutputParameters 35 36 // uses reference parameter x to modify caller's variable 37 void SquareRef( ref int x ) 38 { 39 x = x * x; // squares value of caller's variable 40 } // end method SquareRef 41 42 // uses output parameter x to assign a value 43 // to an uninitialized variable 44 void SquareOut( out int x ) 45 { 46 x = 6; // assigns a value to caller's variable 47 x = x * x; // squares value of caller's variable 48 } // end method SquareOut 49 50 // parameter x receives a copy of the value passed as an argument, 51 // so this method cannot modify the caller's variable 52 void Square( int x ) 53 { 54 x = x * x; 55 } // end method Square 56 } // end class ReferenceAndOutputParameters |
Figure 7.19. Application to test class ReferenceAndOutputParameters.
1 // Fig. 7.19: ReferenceAndOutputParamtersTest.cs 2 // Application to test class ReferenceAndOutputParameters. 3 class ReferenceAndOutputParamtersTest 4 { 5 static void Main( string[] args ) 6 { 7 ReferenceAndOutputParameters test = 8 new ReferenceAndOutputParameters(); 9 test.DemonstrateReferenceAndOutputParameters(); 10 } // end Main 11 } // end class ReferenceAndOutputParamtersTest
|
Method SquareOut (lines 4448) assigns its parameter the value 6 (line 46), then squares that value. SquareOut's parameter is declared as out int, which indicates that the argument passed to this method must be an integer that is passed by reference and that the argument does not need to be initialized in advance.
Method Square (lines 5255) multiplies its parameter x by itself and assigns the new value to x. When this method is called, a copy of the argument is passed to the parameter x. Thus, even though parameter x is modified in the method, the original value in the caller is not modified.
Method DemonstrateReferenceAndOutputParameters (lines 834) invokes methods SquareRef, SquareOut and Square. This method begins by initializing variable y to 5 and declaring, but not initializing, variable z. Lines 1819 call methods SquareRef and SquareOut. Notice that when you pass a variable to a method with a reference parameter, you must precede the argument with the same keyword (ref or out) that was used to declare the reference parameter. Lines 2324 display the values of y and z after the calls to SquareRef and SquareOut. Notice that y has been changed to 25 and z has been set to 36.
Lines 2728 call method Square with y and z as arguments. In this case, both variables are passed by valueonly copies of their values are passed to Square. As a result, the values of y and z remain 25 and 36, respectively. Lines 3233 output the values of y and z to show that they were not modified.
|