ref returns

C# 7.0 NETFx 4.7

Published Updated Author Jeffrey T. Fritz Reading time

Ref returns hand back a reference to existing storage so callers can work in place instead of through a copy.

Ref returns arrived in C# 7.0 as part of the language’s lower-level performance toolset. Instead of returning a value copy, a method can return an alias to an existing variable, array element, or field.

That is powerful when the caller genuinely needs to mutate or inspect the original storage location, but it also means the method is exposing part of its internal memory model directly.

Why it matters

A ref return can remove repeated lookups and extra copies when you are working with arrays, buffers, and other performance-sensitive storage. It pairs well with ref locals, which let the caller keep a stable alias to that returned location.

Used carefully, it gives you targeted in-place updates without forcing the rest of the API surface into unsafe code.

Cautions

You can only return references to storage that will still be alive after the method exits, such as an array element or a field on an existing object. You cannot return a ref to a local variable that is about to disappear.

Because a ref return exposes mutation directly, reserve it for cases where that coupling is intentional and beneficial. Many APIs are still clearer with ordinary return values.

Return the storage slot instead of a copy

A ref return lets the caller update the original array element without searching twice or copying values around.

Valid since C# 7.0

using System;

public static class Program
{
    public static void Main()
    {
        int[] scores = new[] { 78, 84, 91 };
        ref int bestScore = ref FindLargest(scores);
        bestScore += 5;

        Console.WriteLine(string.Join(", ", scores));
    }

    private static ref int FindLargest(int[] values)
    {
        int largestIndex = 0;

        for (int index = 1; index < values.Length; index++)
        {
            if (values[index] > values[largestIndex])
            {
                largestIndex = index;
            }
        }

        return ref values[largestIndex];
    }
}

Expose an in-place update path from a type

Types that own arrays or buffers can offer targeted mutation without exposing the whole storage implementation.

Valid since C# 7.0

using System;
using System.Linq;

public sealed class ScoreBoard
{
    private readonly int[] _scores = new int[4];

    public ref int this[int index] => ref _scores[index];

    public override string ToString() => string.Join(", ", _scores.Select(score => score.ToString()));
}

public static class Program
{
    public static void Main()
    {
        ScoreBoard board = new ScoreBoard();
        board[0] = 10;

        ref int lateGameBonusSlot = ref board[2];
        lateGameBonusSlot = 42;

        Console.WriteLine(board);
    }
}

Learn more

Method parameters and ref returns (Microsoft Learn)

Newer capabilities

  1. ref readonly returns

    Introduced in C# 7.2

    C# 7.2 adds ref readonly returns and locals, which let you avoid copying large value types while still preventing callers from mutating the referenced value.