NDepend Blog

Improve your .NET code quality with NDepend

C# 13 ref struct interfaces and the ‘allows ref struct’ generic anti-constraint

December 2, 2024 4 minutes read

C# ref struct interface

C# 13 will allow interfaces on ref struct. Until now, without this possibility ref struct missed out on abstraction. For example, while Span<T> acts like a sequential list, it cannot be used with methods that accept IReadOnlyList<T> or a IEnumerable<T>. This requires separate methods for Span<T> with nearly identical implementations. Allowing ref struct to implement interfaces enables consistent abstraction and operation reuse across different types.

A Word on ref struct

C# 7.2 introduced ref struct to get the benefit of pointers in a safe context and to prevent memory allocation in many common situations.

A ref struct is a structure that can only exist on the thread stack. To enforce this, the C# compiler imposes several constraints, such as preventing the boxing of a ref struct instance and ensuring that a field of a non ref struct type cannot be typed with a ref struct.

ref structs are designed to enhance performance. The most well-known ref struct is Span<T>. Internally, Span<T> holds a managed pointer that can only exist on the thread stack. A managed pointer is a special .NET runtime feature that has been available since .NET 1.0. Before the introduction of Span<T>, managed pointers were only used for parameter passing by reference through the ref and out keywords.

Managed pointers can point to various types of memory, such as a character within a string, an item in an array, a field within an object or even unmanaged memory. They are tracked by the Garbage Collector and are faster than regular object references due to their stack-only nature. These advantages motivated Microsoft to create Span<T> and ref struct to leverage the power of managed pointers.

More explanations in details in this article: Managed pointers, Span, ref struct, C#11 ref fields and the scoped keyword

Wait, interface means boxing?

Usually, interface on structure leads to boxing. This program…

…compiles to this IL with the box instruction:

Boxing a value on the heap is incompatible with a ref struct that must reside on the thread stack. However, there is an exception where using an interface on a structure does not result in boxing: within a generic parameter context. The following program avoids any boxing:

The anti-constraint ‘allows ref struct’ on generic type

Until C# 13, it was not possible to use a ref struct as a generic type parameter:

ref struct as generic paremeter type error

This is why C# 13 introduces the anti-constraint allows ref struct on generic type. In C# 13 this program compiles:

allows ref struct is the first C# anti-constraint on a generic type: instead of imposing a restriction, it allows a broader range of types to be used as generic parameters.

Putting it all together

In the .NET 9.0, Span<T> and ReadOnlySpan<T> do not implement any interface. This is likely due to potential breaking changes with their stack-only nature being incompatible with the enumerator pattern. Indeed, this pattern requires an IEnumerator<T> object on the heap at some point. However, to implement an abstract algorithm that can work with both ReadOnlySpan<T> and char[], we can still create a wrapper ref struct that implements IReadOnlyList<T> without an enumerator. Here is the code:

allows ref struct and Alternate Lookup for Dictionary and HashSet in .NET 9

For an explanation of .NET 9 alternate lookup please refer to this article: Alternate Lookup for Dictionary and HashSet in .NET 9. It contains this code sample:

We can see that ReadOnlySpan<T> is used as a generic type parameter both at method GetAlternateLookup<ReadOnlySpan<char>>() call site, and in the nested type Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>>. This is because both declarations rely on allows ref struct:

Conclusion

With ref struct interfaces, Microsoft provides a new tool to enhance the performance of C# programs with no design sacrifice thanks to abstraction. Since 2017, there has been ongoing discussion about the possibility of ref struct implementing interfaces. There, some developers exposed real-world examples where this language feature could shine.

In our last example, we had to use a wrapper to use a span as an IReadOnlyList<char>. I wonder if anything will be done to prevent this glitch.

 

Leave a Reply

Your email address will not be published. Required fields are marked *