Yes you can create classes on the stack!

11/1/2023
2 minute read

In this article we will create a class, aka a reference type on the stack, and therefore don't use any managed memory!

Here is the "simple" code:

public record MyClass(int Number);

var typeHandleValue = typeof(MyClass).TypeHandle.Value;
var size = Marshal.ReadInt32(typeHandleValue, 4);
var mem = stackalloc byte[size];
*(IntPtr*)mem = typeHandleValue;
var instance = Unsafe.AsRef<MyClass>(&mem);

The whole idea is to create a pointer to the type handle of the class and then use the pointer to create an instance of the class on the stack. We are using stackalloc to allocate the memory on the stack and then use Unsafe.AsRef to create a reference to the allocated memory. Is it useful? Probably not, but it is possible! And that is all I wanted to show.

The "main issues" with this approach:

  • You are not calling any constructor
  • Your fields are all "uninitialized" - in the sense that integers are not necessarily 0
  • The size has to be static. You can't increase or decrease the size.

So, all in all: Yes, this is a class coated as a struct! With all its downside and almost no upsides.

Here is a small benchmark:

[MemoryDiagnoser]
public class Benchmarks
{
    private static readonly IntPtr TypeHandleValue = typeof(MyClass).TypeHandle.Value;
    private static readonly int Size = Marshal.ReadInt32(TypeHandleValue, 4);

    [Benchmark]
    public unsafe MyClass OnStack()
    {
        var mem = stackalloc byte[Size];
        *(IntPtr*)mem = TypeHandleValue;
        return Unsafe.AsRef<MyClass>(mem);
    }

    [Benchmark]
    public MyClass OnHeap() => new MyClass(0);
}

public record MyClass(int Number);

With the following result:

|  Method |      Mean |     Error |    StdDev |   Gen0 | Allocated |
|-------- |----------:|----------:|----------:|-------:|----------:|
| OnStack | 0.6014 ns | 0.0136 ns | 0.0127 ns |      - |         - |
|  OnHeap | 1.1356 ns | 0.0270 ns | 0.0252 ns | 0.0029 |      24 B |

As we can see, OnStack has a better runtime and doesn't allocate any memory. And Number will also be initialized to something - not necessarily 0. But hey, I never mentioned that the executed program is correct!

Heap, Stack, Boxing and Unboxing, Performance ... let's order things!

In this article I will shade some lights on some of the most used terms which seems very confusing especially for beginners: heap, stack and boxing and unboxing.

Furthermore we will also encounter internet wisdom like:

Value types get stored on the stack. Reference types on the heap

We discuss why this is wrong and what the hell performance has to do with it?

Missing Stack trace when eliding the await keyword

You may have heard that when you elide the await keyword in a method that returns a Task or Task<T>, you lose the stack trace. Buy why does that happen? Let's find out!

Memory is complicated

This is a small story about how memory operates in your .NET application. Well not only .NET but how memory does or does not get allocated.

We will see how a 1 Gigabyte big array is only a few megabytes big to some extend. Furthermore I will discuss working set and committed memory.

An error has occurred. This application may no longer respond until reloaded. Reload x