16 November

C# 11 – ref fields and ref scoped variable

min. read

Reading Time: 4 minutes
C# 11
C# 11

Let’s list all the features that C# 11 brings to the table. Let’s discuss them all one by one, with their use cases, and see how they can come in handy. At the bottom of each article, you can find a link to all 14 new C# features!

The ref fields

This change is part of “Low Level Struct Improvements”. It probably won’t be used by any ordinary programmer in their day-to-day life, but all of us will see improvements when it comes down to performance in critical hot paths.

Previous versions of C# brought us Span<T> that allow programmers to leverage direct memory access with the safety of GC-managed language. Unfortunately, it was using an internal type called ByReference<T> which means that we could leverage Span but we could not implement the Span equivalent ourselves.

In C# 11 this type ByReference<T> will be replaced internally and exposed to everyone by defining the type as ref struct and ref field. So as of today in C# 11 internal Span will look something like this.

readonly ref struct Span<T>
{
    readonly ref T _field;
    readonly int _length;

    // This constructor does not exist today but will be added as a part 
    // of changing Span<T> to have ref fields. It is a convenient, and
    // safe, way to create a length one span over a stack value that today 
    // requires unsafe code.
    public Span(ref T value)
    {
        _field = ref value;
        _length = 1;
    }
}

The field denoted with ref is forced by the compiler to not outlive the value to which it refers.

The ref field as shown can only be used in the ref struct. A ref field can be null and should be checked if that’s the case unless properly initialized.

public ref struct RefFieldExample
{
    private ref int number;

    public int GetNumber()
    {
        if (System.Runtime.CompilerServices.Unsafe.IsNullRef(ref number))
        {
            throw new InvalidOperationException("The number ref field is not initialized.");
        }

        return number;
    }
}

The ref field comes in 3 readonly variants. Which might get confusing to some.

You can apply the readonly modifier to a ref field in the following ways:

  • readonly ref: You can ref reassign such a field with the = ref operator only inside a constructor or an init accessor. You can assign a value with the = operator at any point allowed by the field access modifier.
  • ref readonly: At any point, you cannot assign a value with the = operator to such a field. However, you can ref reassign a field with the = ref operator.
  • readonly ref readonly: You can only ref reassign such a field in a constructor or an init accessor. At any point, you cannot assign a value to the field.

The ref scoped and scoped

The scoped modifier will be used to restrict the lifetime of a value. This as a result will make sure that we keep the object’s lifetime but allows us to cross some boundaries that were previously unreachable.

The scoped notation

Before if you declared a value type on the stack, for example with stackalloc

Span<char> values = stackalloc char[3] { 'T', 'o', 'm' };

you could not pass it into another ref value type. So the following would be forbidden.

Span<char> values = stackalloc char[3] { 'T', 'o', 'm' };
new Test().TestMethod(values); // Cannot use local variable 'values' in this context because it may expose referenced variables outside of their declaration scope

ref struct Test
{
    public void TestMethod(ReadOnlySpan<char> characters)
    {
        // Body of the method doesn't matter
    }
}

This is because the compiler needs to ensure that the stack-allocated memory will not outlive the ref struct that we created.

This is not a problem for classes because, they would copy the value from the allocation, as well as the struct.

The motivation here is to allow such code to work, and take the benefits that ref struct creates.

A full example with scoped keyword would look like this.

Span<char> values = stackalloc char[3] { 'T', 'o', 'm' };
new Test().TestMethod(values);

ref struct Test
{
    public void TestMethod(scoped ReadOnlySpan<char> characters)
    {
        // The body of the method must only use characters in the local scope, and cannot assign it directly to any field or classes unles they themselves would be scoped.
    }
}

The scoped ref notation

This could be also useful for parameters that should are not passed by reference by default. For example, you can now do

Span<int> CreateSpan(scoped ref int parameter)
{
    // body
}

This will allow us to pass the integer by reference, but the compiler will assure that it’s not saved anywhere inside of the scope of the function. This is all to not create empty pointers and handlers.

The scoped variable notation

If we want to keep the reference from escaping, what we can also do is provide the scoped notation to local variables.

scoped Span<int> span stackalloc int[10];

This again will enable the compiler to ensure that you can extend the scope of this variable outside of the default scope, but won’t be referenced and will not persist in other places where it should not.

Summary

Those are very niche topics and I doubt anyone will use them in their career as such micro-optimizations are only valid when optimizing language themselves rather than some generic Web API.

Further reading at dotnet Github page.

Current article:
ref fields and ref scoped variable


Author

Tomasz Juszczak

CTO /

Technical Lead

Tomasz Juszczak

About prog

Founded in 2016 in Warsaw, Poland. Prographers mission is to help the world put the sofware to work in new ways, through the delivery of custom tailored 3D and web applications to match the needs of the customers.


SIMILAR POSTS

What is SaaS?

min. read




Let's talk

SEND THE EMAIL