In the C# programming language, structs or structure types are value types that allow faster access because they are usually stored in the stack. However, although structs reduce memory footprints and eliminate garbage collection overheads, they are not a good choice in high-performance scenarios where memory allocation and deallocation in the stack is critical.
In such scenerios, C# provides a better alternative called a ref struct. Whereas both structs and ref structs are allocated in the stack and don’t require garbage collection, there are subtle differences between the two types and their use cases.
In this article we’ll examine ref structs, their features and benefits, and how we can use them in C#. To work with the code examples provided in this article, you should have Visual Studio 2022 Preview installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here.
Create a console application project in Visual Studio 2022
First off, let’s create a .NET Core 9 console application project in Visual Studio 2022. Assuming you have Visual Studio 2022 installed, follow the steps outlined below to create a new .NET Core 9 console application project.
- Launch the Visual Studio IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window, specify the name and location for the new project.
- Click Next.
- In the “Additional information” window shown next, choose “.NET 9.0 (Standard Term Support)” as the framework version you would like to use.
- Click Create.
We’ll use this .NET 9 console application project to work with ref structs in the subsequent sections of this article.
What is a ref struct?
In the C# programming language, a ref struct is a stack-allocated value type. In contrast to regular structs, you cannot box or unbox ref struct instances. Because ref structs remain in the stack throughout their life cycle, they are a great choice for high-performance applications where resource consumption is critical. The ref struct keyword was first introduced in C# 7.2. However, C# 13 extends their support in a very useful way. With C# 13, we can now use local variables of the ref or ref struct type in iterator and asynchronous methods.
Benefits of using ref structs
The ref struct type has two main benefits — enhanced performance and a deterministic lifetime. Let’s first discuss performance.
Because ref structs reside on the stack only, instances of ref struct are allocated and deallocated much faster than instances of classes that reside on the managed heap. Because there is no garbage collection overhead, ref structs enable much faster data access as well.
Let us understand this with a code example. The following piece of code demonstrates how you create a ref struct.
public ref struct MyRefStruct
{
//Omitted for brevity
}
The following code snippet shows how you can use a ref struct with a Span in C#. Note how the parameter of type Span in the constructor of the ref struct is assigned to the private Span instance of the class.
public ref struct MyRefStruct
{
private Span myPrivateSpan;
public MyRefStruct(Span mySpan)
{
myPrivateSpan = mySpan;
}
public int this[int index]
{
get => myPrivateSpan[index];
set => myPrivateSpan[index] = value;
}
}
Because a ref struct instance doesn’t require any heap allocation or boxing overheads when assigned to any other object, memory overheads are reduced and data access is accelerated. You can use BenchmarkDotNet to collect performance metrics when using ref struct instances in your application.
The deterministic lifetime of ref structs is also an important benefit. Because instances of type ref struct are always allocated on the stack, they will be automatically deallocated from the stack memory when your program no longer requires them. Hence, instances of ref struct are a good choice for creating temporary objects.
The following code snippet illustrates how we can create an instance of a ref struct inside a method.
public class MyClass
{
public void MyMethod()
{
MyRefStruct myRefStruct = new MyRefStruct();
}
}
In this example, as soon as the control moves out of the scope in which the ref struct instance has been created, the instance will be destroyed and the memory it occupied in the stack will be deallocated. So, in this example, we can say that the lifetime of the ref struct instance is restricted to the method scope.
Limitations of a ref struct in C#
With C# 13, a few longstanding limitations of the ref struct type have been removed. With C# 13, a ref struct instance now can be used in iterators and asynchronous methods. In addition, a ref struct in C# 13 can implement interfaces and can be used as a type argument.
Nevertheless, a ref struct in C# still has many limitations. You should keep the following key limitations in mind:
- An instance of type ref struct cannot be an element of an array.
- A ref struct cannot be boxed to either System.Object or System.ValueType.
- A ref struct cannot be a member of a struct.
- You cannot use a ref struct as a generic argument when calling methods.
- You cannot capture an instance of a ref struct in a local function or a lambda expression.
- You cannot convert an instance of type ref struct to an instance of the type of the interface it implements.
- A ref struct must implement all members of an interface it implements (even if the members of the interface have been implemented by default in the interface).
Consider the following code that shows how a ref struct implements an interface.
public interface MyInterface
{
public void MyInterfaceMethod()
{
Console.WriteLine("Inside MyMethod");
}
}
public ref struct MyRefStruct: MyInterface
{
public void MyRefStructMethod()
{
Console.WriteLine("Inside MyRefStructMethod");
}
}
Note that because we have not implemented the interface method inside the ref struct, the compiler will generate an error, as shown in Figure 1.
IDG
Use the dispose pattern with ref structs
You can implement the dispose pattern using a ref struct. To do this, you should define a Dispose method inside the ref struct that has a void return type and doesn’t accept any parameters. Because C# 13 allows you to define a ref struct that implements an interface, you can implement the dispose pattern by defining a ref struct that implements the IDisposable interface. The following code snippet shows how you can do this.
public ref struct MyDisposableRefStruct
{
public void Dispose()
{
//Omitted for brevity
}
}
Besides reducing memory consumption and eliminating garbage collection overheads, another significant benefit of a ref struct is that you can use it with pointers in a safe context. A ref struct is a great choice in applications where you need stack-only allocation and high performance. However, you should be careful to avoid overusing ref structs in your application. For example, if you’re using ref structs in recursive methods while having low memory in the stack, you may encounter performance issues.
How to use ref structs in C# 13 | InfoWorldRead More