Udostępnij za pośrednictwem


C# 7 Series, Part 7: Ref Returns

C# 7 Series

Part 1: Value Tuples
Part 2: Async Main
Part 3: Default Literals
Part 4: Discards
Part 5: Private Protected
Part 6: Read-only structs
Part 7: (This post) Ref Returns

Background

There are two ways to pass an value into a method:

  1. Pass by value. When an argument is passed into a method, a copy of the argument (if it is a value type) or a copy of the argument reference (if it is a reference type) is passed. When you change the argument in the method, the changes (single assignment or compound assignments) is reflected to the copy of the argument/argument reference and it is not reflected to the argument or argument reference itself. This is the default Way in .NET languages (ByVal in Visual Basic.)
  2. Pass by reference. When an argument is passed into a method, either the argument itself (if it is a value type) or the argument reference (if it is a reference type) is directly passed. No other copies are generated. When you change the argument in the method, the changes (single assignment or compound assignments) will reflect to the argument or argument reference itself. This is indicated by using ref keyword in C# or ByRef keyword in Visual Basic.

For example, the FCL’s Array.Resize() method takes a ref parameter of type Array, this ref value is modified in the method implementation to point to the new space of the array after the resize. You will be able to continue using the variable for that array and access the new space:

 byte[] data = new byte[10];
Array.Resize(ref data, 20);
Console.WriteLine($"New array size is: {data.Length}");

Method can also return a value to the caller, but before C# 7.0, all returns are by value by default.

With C# 7.0 or later, ref returns are supported.

Ref Returns

Ref Returns are method return values by references. Similar to ref values passed as method arguments, ref returns can be modified by the caller, and any changes (assignments) to the value will reflect to the original value returned from the method.

In C#, you can make a ref return using the return ref keywords.

Please see the following example.

 private static ref T ElementAt<T>(ref T[] array, int position)
{
     if (array == null)
     {
         throw new ArgumentNullException(nameof(array));
     }

     if (position < 0 || position >= array.Length)
     {
         throw new ArgumentOutOfRangeException(nameof(position));
     }

     return ref array[position];
}

The purpose of the above method is to obtain a reference to the element at the specific position of an array. Later you can use this reference to change the value of the element; because it is a ref value, the changes will apply to the original value in the array.

To use this method, use ref locals:

 private static void Main(string[] args)
{
    int[] data = new int[10];
    Console.WriteLine($"Before change, element at 2 is: {data[2]}");
    ref int value = ref ElementAt(ref data, 2);
    // Change the ref value.
    value = 5;
    Console.WriteLine($"After change, element at 2 is: {data[2]}");
}

The Visual Studio IntelliSense will indicate the calling method is a ref return method.

image

The output of this code is as below:

image

Call Methods with Ref Returns

As previous example, to get a reference of a ref return value, you will need to use ref locals and also put the ref keyword in front of the method (ref in both left and right.)

 ref int value = ref ElementAt(ref data, 2);

You can also call this method without the ref keyword, making the value returned by value.

 int value = ElementAt(ref data, 2);

In this case, the program output will be as the following:

image

However, you need either specify ref in both sides, or not have ref in both sides; you cannot specify ref in one side and not specify ref in the other side.

Additionally, the following code also works:

 ElementAt(ref data, 2) = 5;

You will get the same output as the first example. The element at the position 2 is changed from 0 (default value) to 5. This works because the ref returns can appear as an LValue.

Overload Resolutions

Because return type is not part of method signature to consider an overload, the following method definitions may not work.

 public int M(int[] value);
public ref int M(int[] value);

But the following definitions will work, because the parameter is by ref in the first method and is by value in the second method.

 public ref int M(ref int[] value);
public ref int M(int[] value);

Therefore, in order to overload a ref return method, you’ll need to satisfy the parameter rules:

  • Use different count of parameters, or
  • Use different type of parameters, or
  • Use different value passing method (by value or by ref) of parameters.

Restrictions

There are certain restrictions apply.

  1. The value to be ref returned in a method must be a ref local; the source of this ref local can be an actual ref/out argument of this method, or a field in the type where the method is declared.
  2. You cannot ref return a void type.
  3. You cannot ref return a null literal. But you can ref return a ref local whose value is null.
  4. You cannot ref return from an async method, because the return value might be uncertain by the time the async method returns.
  5. Constants and enums are not allowed to be ref returned.

Conclusion

Ref returns is an extension of the C# language, it can improve the performance of the code by reducing the possibility of copying values from method returns. This is useful for those low level programming components such as interoperability, cross-platform, or resource constrained scenarios (Mobile, IoT etc.,) This feature requires no CLR changes because the similar concepts already exist. To use this feature, you will need to use C# language 7.0, by upgrading your Visual Studio version to 2017 or later.