---
title: "struct vs readonly struct vs ref struct vs record struct"
description: "C# knows `struct` since its down of time. But there are also recent additions like `readonly struct`, `record struct`  and `ref struct`.\n\nThis article will show what are the differences between those 4."
updated: 2022-04-08
tags: ["readonly", "struct", "ref", "heap", "stack", "C#"]
canonical: "https://steven-giesel.com/blogPost/2a52cd8d-b3b2-42e4-87e8-d6dc14147ddb/struct-vs-readonly-struct-vs-ref-struct-vs-record-struct"
---

# struct vs readonly struct vs ref struct vs record struct

C# knows a various span of declarations of `struct` "types". In this article I will show what are the differences between a  `struct`, `readonly struct` and `ref struct`. Furthermore what are the limitations between those types.

## struct
Everyone used this in some way or another. A structure is a *value type*, which should ideally represent a single value. Prominent examples would be `DateTime`, `int`, `double` or `TimeSpan` but also things like a `Point2D` which would consist out of a `x` and `y` coordinate would be a valid example. On the [Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct) we'll find a hint when and when not to use `struct`s:

> Avoid ... It will not have to be boxed frequently.

The last part is important and this is where a lot of this article will go to. We should not box our `struct` often. 
Normally value types such as a struct live on the **stack**. The **stack** is a nice way of modelling the liftetime of a variable. In contrast to that we have the heap. I wrote a complete article about the differencs between **stack**, **heap**, **boxing** and **unboxing**: Have a [read here](https://steven-giesel.com/blogPost/9a40d278-9a9f-49fe-bbfd-2d813a58e73e). It is vital to understand those concepts. 

Important to understand is that normally a `struct` or any **value type** will live on that stack. So if you declare a `int` in a function than it lives exactly in that scope and that is all:
```csharp
public void MyFunc()
{
    int a = 2;
}
```

`a` does only exist inside `MyFunc` and therefore only on the stack. But if we use any **value type** for example as a field in a **class** then this variable **has to** live on the heap as the *parent scope / lifetime* lives on the heap. `struct`s allow a lot of things a `class` would allow with some key differences:
 * `struct` can't inherit from other classes or structs (interfaces are possibile)
 * `struct` can't have a destructor
* ...

An example of a struct:
```csharp
public struct Color
{
	public byte Red;
	public byte Green;
	public byte Blue;
	
	public Color() { Red = 0; Green = 0; Blue = 0; }
	
	public void ShiftToGrayscale()
	{
		var avg = (byte)((Red + Green + Blue) / 3);
		Red = Green = Blue = avg;
	}
}
```

## `readonly struct`
The [`readonly struct`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#readonly-struct) was introduced in C# 7.2. The basic idea is to clarify your `struct` as immutable therefore only allowed to read data, but not to mutate any state. Sure you can do this without the `readonly` modifier (like `DateTime` exists since the beginning of the framework itself), but the compiler will help you when you violate that readonly manner.

Our `Color` example from above will throw multiple compiler errors when we declare the `struct` as `readonly` without adopting any of the code: `public readonly struct Color` will give you something like that:
> Compilation error (line 16, col 14): Instance fields of readonly structs must be readonly.  
Compilation error (line 17, col 14): Instance fields of readonly structs must be readonly.  
Compilation error (line 18, col 14): Instance fields of readonly structs must be readonly.  
Compilation error (line 25, col 3): Cannot assign to 'Red' because it is read-only  
Compilation error (line 25, col 9): Cannot assign to 'Green' because it is read-only  
Compilation error (line 25, col 17): Cannot assign to 'Blue' because it is read-only  

The reason is simple: First we have to declare every field as `readonly`. That makes sense, otherwise any caller could just mutate the colors. But that is not all: The `ShiftToGrayscale` method is still mutating the internal state, so to achieve immutability you would return a new color with the given values:

```csharp
public readonly struct Color
{
	public readonly byte Red;
	public readonly byte Green;
	public readonly byte Blue;
	
	public Color() { Red = 0; Green = 0; Blue = 0; }
	
	public Color(byte red, byte green, byte blue)
	{
		Red = red; Green = green; Blue = blue;
	}
	
	public Color ShiftToGrayscale()
	{
		var avg = (byte)((Red + Green + Blue) / 3);
		return new Color (avg, avg, avg);
	}
}
```

That is a huge step and immutability brings a lot of advantages:
* Increased testability
* Thread safety.
* Atomicity of failure.
* Absence of hidden side-effects

Bottom-line: The compiler tries to help you with immutability. But: It can't guarantee that either. If you have mutable lists or classes like the following example, you will be still able to modify them.
```csharp
public readonly struct MyStruct
{
	public readonly List<object> myStuff = new List<object>() {1, 2};
	
	public MyStruct()
	{
	}
	
	public void Mutate()
	{
		myStuff[0] = 3;
	}
}
```

## ref struct
Also with C# 7.2 Microsoft introduced [`ref struct`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#ref-struct). The bottom line is: `ref struct`s are **guaranteed** to live on the stack and can never *escape* to the heap. With that you can achieve low allocation scenarios where you don't put extra pressure on the garbage collector. Now who does guarantee that? The compiler does that for you. You might ask how does he do that? And the answer is simple: He will prohibit every scenario where a value type is moved to the managed heap:

 * A `ref` struct can't be the element type of an array.  
That means no list, no `myrefStructs = new MyRefStructs[]` but the next is fine as it will live on the stack: `myrefStructs = stackalloc MyRefStructs[10];`
 * A `ref struct` can't be a declared type of a field of a class or a non-ref struct.  
The reason is simple: The compiler can't guarantee that your `struct` is not going to the heap itself for all the reasons explained above. As a `class` always lives on the heap it is clear that it is forbidden to have it there as a field.
 * A `ref struct` variable can't be captured by a lambda expression or a local function.  
Lamda expression as well as local functions are outside of the parent scope and would have therefore to be boxed
 * A `ref` struct variable can't be used in an `async` method.  
The reason is how the C# compiler generates code when it encounters an `async` or better an `await` statement. Basically it builds a statemachine, which itself is a class. If you want to know more head over to my presentation including slides [here](https://steven-giesel.com/blogPost/e0eec540-24db-471c-b8fb-1df8530911d6).
 * A `ref struct` variable can't be used in iterators.  
The same reason as `async`: We create a state machine which would extend the scope/lifetime and therefore the `ref struct` would end up on the heap. If you want to know more [here you go](https://steven-giesel.com/blogPost/994467f6-2429-4534-ad43-c0777076ab22).
 * A `ref struct` can't implement interfaces  
Well they can't do that because that would mean that there is a chance that they get boxed.

The usage is very constrained, but still it has it uses. One of the most prominent examples is [`Span<T>`](https://docs.microsoft.com/en-us/dotnet/api/system.span-1?view=net-6.0) introduced in .NET Core 2.1. It enables zero allocation scenarios. And yes `Span<T>` is internally a `ref struct`. I also covered that in greater detail [here](https://steven-giesel.com/blogPost/4cada9a7-c462-4133-ad7f-e8b671987896).

### Bonus: `readonly ref struct`
Both modifiers can also be put together. The semantics would mean that the `struct` should be immutable and thanks to the `ref` keyword is only allowed to live on the **stack**.

## `record struct`
C# 10 introduced [them](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/record-structs#:~:text=A%20record%20struct%20has%20a,already%20present%20in%20the%20struct.). And much like the `record` which was introduced in C# 9.0 and "enhances" the class, the `record struct` enhances a `struct`. It is a nice syntactic sugar from the compiler to give you "pre-defined" implementations of `Equals`, `ToString()` and `GetHashcode`

You can define a struct super easy like that: 
```csharp
public record struct Color(byte Red, byte Green, byte Blue)
{
    public Color ShiftToGrayscale()
	{
		var avg = (byte)((Red + Green + Blue) / 3);
		return new Color (avg, avg, avg);
	}
}
```

Now where is the `ToString()` and other methods? All that does the compiler for you in the background. The result will look something like that:
```csharp
public struct Color : IEquatable<Color>
{
    public byte Red{ ... }

    public byte Green { ... }

    public byte Blue { ... }

    public Color(byte Red, byte Green, byte Blue)
    {
        ....
    }

    public Color ShiftToGrayscale()
    {
        byte num = (byte)((Red + Green + Blue) / 3);
        return new Color(num, num, num);
    }

    [IsReadOnly]
    [CompilerGenerated]
    public override string ToString()
    {
        ....
    }

    [IsReadOnly]
    [CompilerGenerated]
    private bool PrintMembers(StringBuilder builder)
    {
        ....
    }

    [CompilerGenerated]
    public static bool operator !=(Color left, Color right)
    {
        ...
    }

    [CompilerGenerated]
    public static bool operator ==(Color left, Color right)
    {
       ...
    }

    [IsReadOnly]
    [CompilerGenerated]
    public override int GetHashCode()
    {
        ...
    }

    [IsReadOnly]
    [CompilerGenerated]
    public override bool Equals(object obj)
    {
        ...
    }

    [IsReadOnly]
    [CompilerGenerated]
    public bool Equals(Color other)
    {
        if (EqualityComparer<byte>.Default.Equals(<Red>k__BackingField, other.<Red>k__BackingField) && EqualityComparer<byte>.Default.Equals(<Green>k__BackingField, other.<Green>k__BackingField))
        {
            return EqualityComparer<byte>.Default.Equals(<Blue>k__BackingField, other.<Blue>k__BackingField);
        }
        return false;
    }

    [IsReadOnly]
    [CompilerGenerated]
    public void Deconstruct(out byte Red, out byte Green, out byte Blue)
    {
        Red = this.Red;
        Green = this.Green;
        Blue = this.Blue;
    }
}
```

First thing which is important the `record` automatically implement [`IEquatable<T>`](https://docs.microsoft.com/en-us/dotnet/api/system.iequatable-1?view=net-6.0). For the full implementation have a look at the generated code on [Sharplab.io](https://sharplab.io/#v2:EYLgtghglgdgPgAQEwEYCwAoBBmABAJwFMBjAe3wBNcBnAF3wFdjbcBhUgG3IApgBPWoVwAlQhQA0ufoNwBxIoRiTpQgEIcGhAJSYA3plyHcONp3K4AygAsoAM1oAVUvIh9qxCB0LcdGAJD6/n4AbhD4uBDBAOa4ALy4vALa3NyiVADUcgowuJnqmlq4APS42FoA3Jh+fggA7LgwhADuplzh3JFRkp3d0RVVAL6YA0A=).  One of the best websites to understand what your code and syntactic sugar actually does! Nowadays more and more developers are using `record` and `record struct`s as they are super convenient to use.

### Bonus: `readonly record struct`
Sure you can also apply the `readonly` keyword to a `record struct` it will do the same as a "normal" `struct` as a `record` is translated to a normal one, meaning the compiler will enfore certain restrictions to enforce more immutability.

### Bonus `readonly ref record struct`
This one does not exist. Also `ref record struct` for that matter. And the reason is very simple: `record struct` implements `IEquatable<T>` and we saw earlier that `ref struct`s are not allowed to implement any interface.

## Conclusion
I hope I could give you an overview over the different keywords used in combination with `struct`, what they do and where to use them (or even better, when to avoid them).

## Resources
 * `struct` on [Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct)
 * `record struct` on [Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/record-structs#:~:text=A%20record%20struct%20has%20a,already%20present%20in%20the%20struct.)
 * `Span<T>` and low allocation stringbuilder [here](https://steven-giesel.com/blogPost/4cada9a7-c462-4133-ad7f-e8b671987896)
 * Stack, Heap, Boxing and Unboxing [explained](https://steven-giesel.com/blogPost/9a40d278-9a9f-49fe-bbfd-2d813a58e73e)
 * Generator-Functions [explained](https://steven-giesel.com/blogPost/994467f6-2429-4534-ad43-c0777076ab22)