Understanding values and references

Using ref and out parameters

Ordinarily, when you pass an argument to a method, the corresponding parameter is initialized with a copy of the argument. This is true regardless of whether the parameter is a value type (such as an int), a nullable type (such as int?), or a reference type (such as a WrappedInt). This arrangement means that it’s impossible for any change to the parameter to affect the value of the argument passed in. For example, in the following code, the value output to the console is 42, not 43. The doIncrement method increments a copy of the argument (arg) and not the original argument, as demonstrated here:

static void doIncrement(int param) 
{ 
    param++; 
} 

static void Main() 
{ 
    int arg = 42; 
    doIncrement(arg); 
    Console.WriteLine(arg); // writes 42, not 43 
}

In the preceding exercise, you saw that if the parameter to a method is a reference type, any changes made by using that parameter change the data referenced by the argument passed in. The key point is this: although the data that was referenced changed, the argument passed in as the parameter did not. It still references the same object. In other words, although it’s possible to modify the object that the argument refers to through the parameter, it’s not possible to modify the argument itself—for example, to set it to refer to a completely different object. Most of the time, this guarantee is very useful and can help reduce the number of bugs in a program. Occasionally, however, you might want to write a method that actually needs to modify an argument. C# provides the ref and out keywords so that you can do this.

Creating ref parameters

If you prefix a parameter with the ref keyword, the C# compiler generates code that passes a reference to the actual argument rather than a copy of the argument. When using a ref parameter, anything you do to the parameter you also do to the original argument because the parameter and the argument both reference the same data.

When you pass an argument as a ref parameter, you must also prefix the argument with the ref keyword. This syntax provides a useful visual cue to the programmer that the argument might change. Here’s the preceding example again, this time modified to use the ref keyword:

static void doIncrement(ref int param) // using ref 
{ 
    param++; 
} 

static void Main() 
{ 
    int arg = 42; 
    doIncrement(ref arg); // using ref 
    Console.WriteLine(arg); // writes 43 
}

This time, the doIncrement method receives a reference to the original argument rather than a copy, so any changes the method makes by using this reference actually change the original value. That’s why the value 43 is displayed on the console.

Remember that C# enforces the rule that you must assign a value to a variable before you can read it. This rule also applies to method arguments; you cannot pass an uninitialized value as an argument to a method even if an argument is defined as a ref argument. For example, in the following example, arg is not initialized, so this code will not compile. This failure occurs because the statement param++; within the doIncrement method is really an alias for the statement arg++; and this operation is allowed only if arg has a defined value:

static void doIncrement(ref int param) 
{ 
    param++; 
} 

static void Main() 
{ 
    int arg; // not initialized 
    doIncrement(ref arg); 
    Console.WriteLine(arg); 
}

Creating out parameters

The compiler checks whether a ref parameter has been assigned a value before calling the method. However, there might be times when you want the method itself to initialize the parameter. You can do this with the out keyword.

The out keyword is syntactically similar to the ref keyword. You can prefix a parameter with the out keyword so that the parameter becomes an alias for the argument. As when using ref, anything you do to the parameter, you also do to the original argument. When you pass an argument to an out parameter, you must also prefix the argument with the out keyword.

The keyword out is short for output. When you pass an out parameter to a method, the method must assign a value to it before it finishes or returns, as shown in the following example:

static void doInitialize(out int param) 
{ 
    param = 42; // Initialize param before finishing 
}

The following example does not compile because doInitialize does not assign a value to param:

static void doInitialize(out int param) 
{ 
    // Do nothing 
}

Because an out parameter must be assigned a value by the method, you’re allowed to call the method without initializing its argument. For example, the following code calls doInitialize to initialize the variable arg, which is then displayed on the console:

static void doInitialize(out int param) 
{ 
    param = 42; 
} 

static void Main() 
{ 
    int arg; // not initialized 
    doInitialize(out arg); // legal 
    Console.WriteLine(arg); // writes 42 
}

In the next exercise, you’ll practice using ref parameters.

To use ref parameters

  1. Return to the Parameters project in Visual Studio 2022.

  2. Display the Pass.cs file in the Code and Text Editor window.

  3. Edit the Value method to accept its parameter as a ref parameter.

    The Value method should look like this:

    class Pass 
    { 
        public static void Value(ref int param) 
        { 
            param = 42; 
        } 
        ... 
    }
  4. Display the Program.cs file in the Code and Text Editor window.

  5. Uncomment the first four statements.

    Notice that the third statement of the doWork method, Pass.Value(i), indicates an error. The error occurs because the Value method now expects a ref parameter.

  6. Edit this statement so that the Pass.Value method call passes its argument as a ref parameter.

    The doWork method should now look like this:

    class Program 
    { 
      static void doWork() 
      { 
        int i = 0; 
        Console.WriteLine(i); 
        Pass.Value(ref i); 
        Console.WriteLine(i); 
        ... 
      } 
    }
  7. On the Debug menu, select Start Without Debugging to build and run the program.

    This time, the first two values written to the console window are 0 and 42. This result shows that the call to the Pass.Value method has successfully modified the argument i.

  8. Press Enter to close the application and return to Visual Studio 2022.