Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing code for a colleague who was building a high-frequency telemetry system. He had created a record class to represent a SensorReading—just a timestamp, a sensor ID, and a double value. It looked clean, and the value-based equality made his unit tests a breeze. But when we hit the stress-test phase, the application started stuttering. The profiler showed that the Garbage Collector was working overtime, frantically cleaning up millions of these tiny sensor objects that lived for only a few milliseconds. He was paying a massive "heap tax" for data that was essentially just a grouping of primitives.

This is exactly where record struct comes in. It gives you the best of both worlds: the performance of a value type (allocated on the stack or inline) and the syntactic sugar of a record (concise declarations and built-in equality).

Value-Based Equality Without the Boilerplate

If you've used traditional structs before, you know the pain of overriding Equals() and GetHashCode(). If you forget, C# defaults to reflection-based equality, which is notoriously slow. With a record struct, the compiler generates those overrides for you automatically.

public record struct Vertex(float X, float Y, float Z);

var v1 = new Vertex(1.0f, 2.0f, 3.0f);
var v2 = new Vertex(1.0f, 2.0f, 3.0f);

// This is true because it's a record! 
// A regular struct would also be true, but records make the intent explicit.
Console.WriteLine(v1 == v2); 

I personally love this for mathematical primitives. When you're dealing with coordinates, colors, or complex numbers, you care about the value, not whether two objects occupy the same spot in memory. By using a record struct, you're telling the next developer: "This is a data carrier, and its identity is defined by its data."

Managing Mutability and the Readonly Modifier

Here is where things get a bit trippy. Unlike record class (which defaults to immutable properties), a record struct is mutable by default. If you define it using the positional syntax I used above, the properties are public and settable.

In my experience, mutability in structs can lead to some confusing bugs, especially when you're dealing with arrays or lists of them. If you want that "set it and forget it" safety, you should almost always use the readonly modifier.

// This version is immutable. The properties are 'init-only'.
public readonly record struct ColorRgb(byte R, byte G, byte B);

var white = new ColorRgb(255, 255, 255);
// white.R = 0; // This would cause a compiler error.

// But we still get the 'with' expression for non-destructive mutation!
var lightGray = white with { R = 200, G = 200, B = 200 };

The with expression is the real killer feature here. It allows you to "change" a value by creating a new copy with one or two modifications, keeping your data flow predictable while avoiding the overhead of heap allocations. If you're building something where performance is critical but you still want a functional programming style, readonly record struct is your best friend.




📋 Practical Task

Refactoring a Particle System for Memory Efficiency

You are working on a simple 2D particle engine. Currently, the system uses a record class for the Particle definition, and the GC is causing frame drops during heavy explosions.

Your Task:

  • Convert the Particle record class into a readonly record struct.
  • The Particle should contain: float X, float Y, float VelocityX, and float VelocityY.
  • Implement a method called UpdatePosition(float deltaTime) that returns a new particle with the updated X and Y coordinates using the with expression.
  • In your main program, create two identical particles and use the == operator to verify that value-based equality is working correctly.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.