[ Team LiB ] |
4.5 Passing ParametersBy default, value types are passed into methods by value. (See Section 4.1.2, earlier in this chapter). This means that when a value object is passed to a method, a temporary copy of the object is created within that method. Once the method completes, the copy is discarded. Although passing by value is the normal case, there are times when you will want to pass value objects by reference. C# provides the ref parameter modifier for passing value objects into a method by reference, and the out modifier for those cases in which you want to pass in a ref variable without first initializing it. C# also supports the params modifier, which allows a method to accept a variable number of parameters. The params keyword is discussed in Chapter 9. 4.5.1 Passing by ReferenceMethods can return only a single value (though that value can be a collection of values). Let's return to the Time class and add a GetTime( ) method, which returns the hour, minutes, and seconds.
Because we cannot return three values, perhaps we can pass in three parameters, let the method modify the parameters, and examine the result in the calling method. Example 4-7 shows a first attempt at this. Example 4-7. Returning values in parameterspublic class Time { // private member variables private int Year; private int Month; private int Date; private int Hour; private int Minute; private int Second; // public accessor methods public void DisplayCurrentTime( ) { System.Console.WriteLine("{0}/{1}/{2} {3}:{4}:{5}", Month, Date, Year, Hour, Minute, Second); } public int GetHour( ) { return Hour; } public void GetTime(int h, int m, int s) { h = Hour; m = Minute; s = Second; } // constructor public Time(System.DateTime dt) { Year = dt.Year; Month = dt.Month; Date = dt.Day; Hour = dt.Hour; Minute = dt.Minute; Second = dt.Second; } } public class Tester { static void Main( ) { System.DateTime currentTime = System.DateTime.Now; Time t = new Time(currentTime); t.DisplayCurrentTime( ); int theHour = 0; int theMinute = 0; int theSecond = 0; t.GetTime(theHour, theMinute, theSecond); System.Console.WriteLine("Current time: {0}:{1}:{2}", theHour, theMinute, theSecond); } } Output: 11/17/2005 13:41:18 Current time: 0:0:0 Notice that the "Current time" in the output is 0:0:0. Clearly, this first attempt did not work. The problem is with the parameters. We pass in three integer parameters to GetTime( ), and we modify the parameters in GetTime( ), but when the values are accessed back in Main( ), they are unchanged. This is because integers are value types, and so are passed by value; a copy is made in GetTime( ). What we need is to pass these values by reference. Two small changes are required. First, change the parameters of the GetTime( ) method to indicate that the parameters are ref (reference) parameters: public void GetTime(ref int h, ref int m, ref int s) { h = Hour; m = Minute; s = Second; } Second, modify the call to GetTime( ) to pass the arguments as references as well: t.GetTime(ref theHour, ref theMinute, ref theSecond); If you leave out the second step of marking the arguments with the keyword ref, the compiler will complain that the argument cannot be converted from an int to a ref int. The results now show the correct time. By declaring these parameters to be ref parameters, you instruct the compiler to pass them by reference. Instead of a copy being made, the parameter in GetTime( ) is a reference to the same variable (theHour) that is created in Main( ). When you change these values in GetTime( ), the change is reflected in Main( ). Keep in mind that ref parameters are references to the actual original value—it is as if you said, "Here, work on this one." Conversely, value parameters are copies—it is as if you said, "Here, work on one just like this." 4.5.2 Passing Out Parameters with Definite AssignmentC# imposes definite assignment, which requires that all variables be assigned a value before they are used. In Example 4-7, if you don't initialize theHour, theMinute, and theSecond before you pass them as parameters to GetTime( ), the compiler will complain. Yet the initialization that is done merely sets their values to 0 before they are passed to the method: int theHour = 0; int theMinute = 0; int theSecond = 0; t.GetTime( ref theHour, ref theMinute, ref theSecond); It seems silly to initialize these values because you immediately pass them by reference into GetTime where they'll be changed, but if you don't, the following compiler errors are reported: Use of unassigned local variable 'theHour' Use of unassigned local variable 'theMinute' Use of unassigned local variable 'theSecond' C# provides the out parameter modifier for this situation. The out modifier removes the requirement that a reference parameter be initialized. The parameters to GetTime( ), for example, provide no information to the method; they are simply a mechanism for getting information out of it. Thus, by marking all three as out parameters, you eliminate the need to initialize them outside the method. Within the called method, the out parameters must be assigned a value before the method returns. Here are the altered parameter declarations for GetTime( ): public void GetTime(out int h, out int m, out int s) { h = Hour; m = Minute; s = Second; } and here is the new invocation of the method in Main( ): t.GetTime( out theHour, out theMinute, out theSecond); To summarize, value types are passed into methods by value. ref parameters are used to pass value types into a method by reference. This allows you to retrieve their modified value in the calling method. out parameters are used only to return information from a method. Example 4-8 rewrites Example 4-7 to use all three. Example 4-8. Using in, out, and ref parameterspublic class Time { // private member variables private int Year; private int Month; private int Date; private int Hour; private int Minute; private int Second; // public accessor methods public void DisplayCurrentTime( ) { System.Console.WriteLine("{0}/{1}/{2} {3}:{4}:{5}", Month, Date, Year, Hour, Minute, Second); } public int GetHour( ) { return Hour; } public void SetTime(int hr, out int min, ref int sec) { // if the passed in time is >= 30 // increment the minute and set second to 0 // otherwise leave both alone if (sec >= 30) { Minute++; Second = 0; } Hour = hr; // set to value passed in // pass the minute and second back out min = Minute; sec = Second; } // constructor public Time(System.DateTime dt) { Year = dt.Year; Month = dt.Month; Date = dt.Day; Hour = dt.Hour; Minute = dt.Minute; Second = dt.Second; } } public class Tester { static void Main( ) { System.DateTime currentTime = System.DateTime.Now; Time t = new Time(currentTime); t.DisplayCurrentTime( ); int theHour = 3; int theMinute; int theSecond = 20; t.SetTime(theHour, out theMinute, ref theSecond); System.Console.WriteLine( "the Minute is now: {0} and {1} seconds", theMinute, theSecond); theSecond = 40; t.SetTime(theHour, out theMinute, ref theSecond); System.Console.WriteLine("the Minute is now: " + "{0} and {1} seconds", theMinute, theSecond); } } Output: 11/17/2005 14:6:24 the Minute is now: 6 and 24 seconds the Minute is now: 7 and 0 seconds SetTime is a bit contrived, but it illustrates the three types of parameters. theHour is passed in as a value parameter; its entire job is to set the member variable Hour, and no value is returned using this parameter. The ref parameter theSecond is used to set a value in the method. If theSecond is greater than or equal to 30, the member variable Second is reset to 0 and the member variable Minute is incremented.
Finally, theMinute is passed into the method only to return the value of the member variable Minute, and thus is marked as an out parameter. It makes perfect sense that theHour and theSecond must be initialized; their values are needed and used. It is not necessary to initialize theMinute, as it is an out parameter that exists only to return a value. What at first appeared to be arbitrary and capricious rules now make sense; values are only required to be initialized when their initial value is meaningful. |
[ Team LiB ] |