By
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.
Methods 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.
Tip
Java
programmers
take
note: In C#, there is
no need for wrapper classes for basic types like
int
(integer). Instead, use reference parameters.
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 parameters
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/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.”
C# 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 parameters
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/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.
Tip
C
and
C++
programmers
take
note: In C#, you must
specify ref
on both the call and the destination
when using reference parameters.
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.
Get Programming C#, Third 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.