9.3 Returning Multiple ValuesMethods can return only a single value. But this isn't always convenient. Let's return to the Time class. It would be great to create a GetTime() method to return the hour, minutes, and seconds. You can't return all three of these as return values, but perhaps you can pass in three parameters, let the GetTime() method modify the parameters, and then examine the result in the calling method, in this case Run(). Example 9-3 is a first attempt. Example 9-3. Retrieving multiple values, first attemptusing System; namespace PassByRef { public class Time { // private member variables private int Year; private int Month; private int Date; private int Hour; private int Minute; private int Second; // Property (read only) public int GetHour() { return Hour; } // public accessor methods public void DisplayCurrentTime() { System.Console.WriteLine("{0}/{1}/{2} {3}:{4}:{5}", Month, Date, Year, Hour, Minute, Second); } 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; } } class Tester { public void Run() { 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); } static void Main() { Tester t = new Tester(); t.Run(); } } } Output: 3/26/2002 12:22:19 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 Run(), they are unchanged. This is because integers are value types. 9.3.1 Passing by Value and by ReferenceC# divides the world of types into value types and reference types. All intrinsic types (such as int and long) are value types. Classes are reference types. When you pass a value type (such as an int) into a method, a copy is made. When you make changes to the parameter, you make changes to the copy. Back in the Run() method, the original integer variables, theHour, theMinute, and theSecond are unaffected by the changes made in GetTime(). What you need is a way to pass in the integer parameters by reference. When you pass an object by reference, no copy is made. A reference to the original variable is passed in. Thus when you make changes in GetTime(), the changes are also made to the original variables in Run(). This requires two small modifications to the code in Example 9-3. 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: t.GetTime(ref theHour, ref theMinute, ref theSecond);
These changes are shown in Example 9-4. Example 9-4. Passing by referenceusing System; namespace PassByRef { public class Time { // private member variables private int Year; private int Month; private int Date; private int Hour; private int Minute; private int Second; // Property (read only) public int GetHour() { return Hour; } // public accessor methods public void DisplayCurrentTime() { System.Console.WriteLine("{0}/{1}/{2} {3}:{4}:{5}", Month, Date, Year, Hour, Minute, Second); } // takes references to ints public void GetTime(ref int h, ref int m, ref 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; } } class Tester { public void Run() { System.DateTime currentTime = System.DateTime.Now; Time t = new Time(currentTime); t.DisplayCurrentTime(); int theHour = 0; int theMinute = 0; int theSecond = 0; // pass the ints by reference t.GetTime(ref theHour, ref theMinute, ref theSecond); System.Console.WriteLine("Current time: {0}:{1}:{2}", theHour, theMinute, theSecond); } static void Main() { Tester t = new Tester(); t.Run(); } } } Output: 3/26/2002 12:25:41 Current time: 12:25:41 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 parameters in GetTime() are references to the corresponding variables (theHour, theMinute, theSecond) that were created in Run(). When you change these values in GetTime(), the change is reflected in Run(). 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." 9.3.2 Out Parameters and Definite AssignmentAs noted in Chapter 5, C# imposes definite assignment, which requires that all variables be assigned a value before they are used. In Example 9-4, 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 modifier for situations like this, in which initializing a parameter is only a formality. 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 using the out keyword, 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. |