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
8-3 is a first attempt.
Example 8-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; // 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 theHour, int theMinute, int theSecond ) { theHour = Hour; theMinute = Minute; theSecond )= 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(); } } }
The output will look something like this:
7/1/2008 12:22:19Current 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( )
, and you modify the parameters in GetTime( )
, but when the values are accessed
back in Run( )
, they are unchanged.
This is because integers are value types.
As discussed in Chapter 7, C# divides the world of
types into value types and reference types. All intrinsic types (such
as int
and long
) are value types. Instances of classes
(objects) 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 so that changes made in the method are made to the original
object in the calling method. When you pass an object by reference,
the parameter refers to the same object. Thus when you make changes in
GetTime( )
, the changes are also
made to the original variables in Run( )
.
Please ignore this note as it is advanced, confusing, and put here just to cut down on my email. Technically, when you pass a reference type, it is in fact passed by value; but the copy that is made is a copy of a reference, and thus that copy points to the same (unnamed) object on the heap as did the original reference object. That is how you achieve the semantics of “pass by reference” in C# using pass by value.
This is the last time I’ll point out this esoteric idea as it is perfectly reasonable to consider the reference to be the object. It takes too long to refer to Fido as a “reference to an unnamed Dog object on the heap”; it is easier to say Fido is a Dog object, and it is reasonable to imagine that Fido is passed by reference, even though (technically) we know better.
Not only is this shorthand reasonable, it is how most professional programmers think and talk about it. In most cases, it comes down to a distinction without a meaningful difference.
This requires two small modifications to the code in Example 8-3. First, change
the parameters of the GetTime( )
method to indicate that the parameters are ref
(reference) parameters:
public void GetTime( ref int theHour, ref int theMinute, ref int theSecond ) { theHour = Hour; theMinute = Minute; theSecond = 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 8-4.
Example 8-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;
// 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 theHour,
ref theMinute,
ref theSecond )
{
theHour = Hour;
theMinute = Minute;
theSecond = 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 referencet.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();
}
}
}
This time, the output looks like this:
7/1/2008 12:25:41Current 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.”
As noted in Chapter
4, C# imposes definite assignment, which
requires that all variables be assigned a value before they are used.
In Example 8-4, you
initialize theHour
, theMinute
, and theSecond
before you pass them as parameters
to GetTime( )
, yet the
initialization 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 theHour, out int theMinute, out int theSecond ) { theHour = Hour; theMinute = Minute; theSecond = Second; }
Here is the new invocation of the method in Main( )
:
int theHour; int theMinute; int theSecond; t.GetTime( out theHour, out theMinute, out theSecond);
The keyword out
implies the
same semantics as the keyword ref
,
except that it also allows you to use the variable without first
initializing it in the calling method.