C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
232: Record Structs
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
Particlerecord class into areadonly record struct. - The
Particleshould contain:float X,float Y,float VelocityX, andfloat VelocityY. - Implement a method called
UpdatePosition(float deltaTime)that returns a new particle with the updated X and Y coordinates using thewithexpression. - In your main program, create two identical particles and use the
==operator to verify that value-based equality is working correctly.
There are no comments for now.