16 November
C# 11 – ref fields and ref scoped variable
Programming
min. read
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 aninit
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 aninit
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
Let's talk
I agree that my data in this form will be sent to [email protected] and will be read by human beings. We will answer you as soon as possible. If you sent this form by mistake or want to remove your data, you can let us know by sending an email to [email protected]. We will never send you any spam or share your data with third parties.
I agree that my data in this form will be sent to [email protected] and will be read by human beings. We will answer you as soon as possible. If you sent this form by mistake or want to remove your data, you can let us know by sending an email to [email protected]. We will never send you any spam or share your data with third parties.