By default, value types are passed into methods by value. (See the section "Method Arguments" 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 variable by reference without first initializing it.
C# also supports the params
modifier, which allows a method to accept a variable number of parameters. We discuss the params
keyword in Chapter 9.
Methods can return only a single value (though that value can itself 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 you can't return three values, perhaps you can pass in three parameters, let the method modify the parameters, and examine the result in the calling method. Example 4-8 shows a first attempt at this.
Example 4-8. Returning values in parameters
using System; namespace ReturningValuesInParams { public 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/2007 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. You pass in three integer parameters to GetTime( )
, where they are modified, 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 as they are passed to GetTime( )
, and the original values in Main( )
are left unchanged. What you 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:
t.GetTime(ref
theHour,ref
theMinute,ref
theSecond);
Warning
If you leave out the second step of marking the arguments with the keyword ref
, the compiler will complain that the argument can't be converted from an int
to a ref int
.
The results will now show the correct time. By declaring these parameters to be ref
parameters, you instruct the compiler to pass them by reference, and instead of a copy being made, the parameter in GetTime( )
will be a reference to the same variable (theHour
) that was created in Main( )
. When you change these values in GetTime( )
, the change will be reflected in Main( )
because the references will be referring to the same values in memory.
Keep in mind that ref
parameters are references to the actual original value: it is as though you said, "Here, work on this one." Conversely, value parameters are copies: it is as though you said, "Here, work on one just like this."
C# imposes definite assignment, which requires that all variables be assigned a value before they are used. In Example 4-8, 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);
There is no logical reason to set these values to zero; our intent is to pass them to GetTime
and have them set to a meaningful value there. The problem is that definite assignment won't let unassigned variables be passed as parameters. If you try, you will receive the following compiler errors:
Use of unassigned local variable 'theHour' Use of unassigned local variable 'theMinute' Use of unassigned local variable 'theSecond'
One solution, as shown here, is to initialize the variables to a meaningless value−in this case, zero. But assigning a value should have semantic meaning within your program; it shouldn't be done just to satisfy a technical requirement that doesn't apply in this circumstance. That is why C# provides the out
parameter modifier.
The out
modifier suspends 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 from the method. Thus, by marking all three as out
parameters, you eliminate the need to initialize them outside the method.
The out
parameter thus increases the self-documentation (and long-term reliability) of the program in two ways:
It removes a misleading assignment that appeared to assign a meaningful value but actually was included just to silence the compiler.
It documents the fact that the three parameters are passed to the
GetTime
method only to extract information and provide no meaningful information on the way in.
Within the called method, the out
parameters must be assigned a value before the method returns (failure to do so will cause a compile error). The following 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 values in the calling method. out
parameters are used only to return information from a method. Example 4-9 rewrites Example 4-8 to use all three.
Example 4-9. Using in, out, and ref parameters
using System;
namespace InOutRef
{
public 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/2007 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 required to be initialized only when their initial value is meaningful.
Get Programming C# 3.0, 5th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.