Team LiB   Previous Section   Next Section

9.3 Returning Multiple Values

Methods 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 attempt
using 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 Reference

C# 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);

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.

These changes are shown in Example 9-4.

Example 9-4. Passing by reference
using 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 Assignment

As 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.

    Team LiB   Previous Section   Next Section