Returning Multiple Values

Methods can return only a single value, but this isn’t always convenient. Suppose you have a class called Doubler, which contains a method we’ll call DoubleInt( ) that takes two integers and doubles them. Simple enough, right?

The problem is that although DoubleInt( ) can accept two integers, and can process them both, it can return only one of them. Example 8-3 shows a way that you might try to write DoubleInt( ).

Example 8-3. This is our first attempt at retrieving multiple values

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example_8_3_ _ _ _Returning_multiple_values
{
    class Doubler
    {
        public void DoubleInt(int firstNum, int secondNum)
        {
            firstNum = firstNum * 2;
            secondNum = secondNum * 2;
        }
    }
    class Tester
    {
        public void Run( )
        {
            int first = 5;
            int second = 10;
            Console.WriteLine("Before doubling:");
            Console.WriteLine("First number: {0}, Second number: {1}",
                              first, second);

            Doubler d = new Doubler( );
            d.DoubleInt(first, second);
            Console.WriteLine("After doubling:");
            Console.WriteLine("First number: {0}, Second number: {1}",
                              first, second);
        }

        static void Main(string[] args)
        {
            Tester t = new Tester( );
            t.Run( );
        }
    }
}

The output will look something like this:

Before doubling:
First number: 5, Second number: 10
After doubling:
First number: 5, Second number: 10

Obviously, that’s not the desired result. The problem is with the parameters. You pass in two integer parameters to DoubleInt( ), and you modify those two parameters in DoubleInt( ), but when the values are accessed back in Run( ) they are unchanged. This is because integers are value types.

Passing Value Types by Reference

As we 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’re actually making changes to the copy. Back in the Run( ) method, the original integer variables—first and second—are unaffected by the changes made in DoubleInt( ).

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 DoubleInt( ), the changes are made to the original variables in Run( ). You do this by prefacing the parameters with the keyword ref.

Tip

Technically, when you pass a reference type, the reference itself is 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. However, that’s all behind-the-scenes stuff, and it’s acceptable to say that you’re passing objects by reference.

This requires two small modifications to the code in Example 8-3. First, change the parameters of the DoubleInt( ) method to indicate that the parameters are ref (reference) parameters:

public void DoubleInt(ref int firstNum, ref int secondNum)

Second, modify the call to DoubleInt( ) to pass the arguments as references:

d.DoubleInt(ref first, ref second);

Tip

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. You can use the ref keyword to pass by reference

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example_8_4_ _ _ _Passing_by_Reference
{
    class Doubler
    {
        public void DoubleInt(ref int firstNum, ref int secondNum)
        {
            firstNum = firstNum * 2;
            secondNum = secondNum * 2;
        }
    }
    class Tester
    {
        public void Run( )
        {
            int first = 5;
            int second = 10;
            Console.WriteLine("Before doubling:");
            Console.WriteLine("First number: {0}, Second number: {1}",
                              first, second);

            Doubler d = new Doubler( );
            d.DoubleInt(ref first, ref second);
            Console.WriteLine("After doubling:");
            Console.WriteLine("First number: {0}, Second number: {1}",
                              first, second);
        }

        static void Main(string[] args)
        {
            Tester t = new Tester( );
            t.Run( );
        }
    }
}

This time, the output looks like this:

Before doubling:
First number: 5, Second number: 10
After doubling:
First number: 10, Second number: 20

These results are more like what you’d expect.

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 DoubleInt( ) are references to the corresponding variables (first and second) that were created in Run( ). When you change these values in DoubleInt( ), the change is reflected in Run( ).

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

out Parameters and Definite Assignment

As we noted in Chapter 4, C# imposes definite assignment, which requires that all variables be assigned a value before they are used. Suppose you have a method for returning all three parameters from a Box object. You’d call the method something like this:

myBox.GetDimensions( ref myLength, ref myWidth, ref myHeight);

Because of definite assignment, though, you’d have to initialize those three variables before you can pass them to your method:

int myLength = 0;
int myWidth = 0;
int myHeight = 0;
myBox.GetDimensions( ref myLength, ref myWidth, ref myHeight);

It seems silly to initialize these values, because you immediately pass them by reference into GetDimensions( ) where they’ll be changed; but if you don’t initialize them, the compiler will raise an error for each of the variables.

C# provides the out modifier for situations such as 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 GetDimension( ), 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 GetDimensions( ):

public void GetDimensions( out int theLength, out int theWidth
                           out int theHeight )
{
    theLength = length;
    theWidth = width;
    theHeight = height;
}

Here is the new invocation of the method in Run( ):

int myLength;
int myWidth;
int myHeight;
myBox.GetDimensions( out myLength, out myWidth, out myHeight);

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset