Ref<T> and ReadOnlyRef<T>


The Ref&<T> is a stack-only type that can store a reference to a value of a specified type. It is semantically equivalent to a ref T value, with the difference that it can also be used as a type of field in another stack-only struct type. It can be used in place of proper ref T fields, which are currently not supported in C#.

Platform APIs: Ref&<T>, ReadOnlyRef<T>

How it works


Due to how it's implemented on different .NET Standard contracts, the Ref<T> has some minor differences on .NET Standard 1.4 and .NET Standard 2.1 (and equivalent .NET Core runtimes). In particular, on .NET Standard 1.4 it can only be used to reference fields within an object, instead of arbitrary values pointed by a managed reference. This is because the underlying APIs being used on .NET Standard 1.4 don't have built-in support for the Span<T> type.

Ref<T> on .NET Standard 1.4

As mentioned before, on .NET Standard 1.4, Ref<T> can only point to locations within a given object. Consider the following code:

// Be sure to include this using at the top of the file:
using Microsoft.Toolkit.HighPerformance;

// Define a sample model
class MyModel
    public int Number = 42;

var model = new MyModel();

// Create a Ref<T> pointing to the MyModel.Number field
Ref<int> byRef = new Ref<T>(model, ref model.Number);

// Modify the field indirectly

Console.WriteLine(model.Number); // Prints 43!


The Ref<T> constructor doesn't validate the input arguments, meaning that it is your responsibility to pass a valid object and a reference to a field within that object. Failing to do so might cause the Ref<T>.Value property to return an invalid reference.

Ref<T> on .NET Standard 2.1

On .NET Standard 2.1, Ref<T> can point to any ref T value:

int number = 42;

// Create a Ref<int> pointing to 'number'
Ref<int> byRef1 = new Ref<int>(ref number);

// Modify 'number'

Console.WriteLine(number); // Prints 43!


This type comes with a few caveats and should be used carefully, as it can lead to runtime crashes if a Ref<T> instance is created with an invalid reference. In particular, you should only create a Ref<T> instance pointing to values that have a lifetime that is greater than that of the Ref<T> in use. Consider the following snippet:

public static ref int GetDummyReference()
    int number = 42;

    Ref<int> byRef = new Ref<T>(ref number);
    return ref byRef.Value;        

This will compile and run fine, but the returned ref int will be invalid (as in, it will not point to a valid location) and could cause crashes if it's dereferenced. It is your responsibility to track the lifetime of values being referenced by new Ref<T> values.


Although it is possible to create a Ref<T> value wrapping a null reference, by using the default(Ref<T>) expression, the Ref<T> type is not designed to be used with nullable references and does not include proper features to validate the internal reference. If you need to return a reference that can be set to null, use the NullableRef<T> and NullableReadOnlyRef<T> types.


The ReadOnlyRef<T> is a stack-only type that mirrors Ref<T>, with the exception that its constructor takes an in T parameter (a readonly reference), instead of a ref T one. Similarly, its Value property has a ref readonly T return type instead of ref T.


You can find more examples in the unit tests.