struct and record struct (value types) usually live on the faster stack, while class and record class (reference types) reside on the heap, incurring garbage collection overhead.struct and especially record struct are often faster for small, frequently accessed data structures, minimizing garbage collection pressure.record struct often benchmarks fastest, the optimal choice depends on data size, immutability needs, required features (like inheritance), and specific application bottlenecks. Don't choose based on speed alone.In C#, the way you define your data structures using class, struct, or record has significant implications for performance. These differences arise from their fundamental nature as either reference types or value types and how they are managed in memory.
Classes are the cornerstone of object-oriented programming in C#. They are reference types. This means when you create an instance of a class (an object), memory is allocated on the heap, and the variable holds only a reference (like a pointer or address) to that memory location.
==) compare references (identity equality), not the object's content.
Visual representation of Stack (for value types and references) and Heap (for reference type objects).
Structs are value types. Variables of a struct type directly contain the data. Typically, structs allocated as local variables or method parameters reside on the stack, which is a highly efficient region of memory.
System.ValueType) and are generally intended for lightweight data representation.Introduced in C# 9 and enhanced in C# 10, records provide a concise syntax for defining types whose primary purpose is storing data. They emphasize immutability and value-based equality.
record class (or just record): A reference type, allocated on the heap like a regular class, but with compiler-generated methods for immutability (using init-only setters by default), value-based equality (Equals, GetHashCode), and non-destructive mutation (with expressions).record struct: A value type, allocated on the stack (like regular structs), offering the benefits of value types combined with the record features like concise syntax, immutability, and value-based equality.Equals and GetHashCode based on the values of their public properties, not their memory addresses. Two record instances are equal if all their corresponding properties are equal.The most significant factor determining the relative speed of classes, structs, and records is how and where they store their data in memory.
Memory in .NET is primarily managed in two areas: the stack and the heap.
int, bool, struct, record struct), method parameters, local variables, and references to heap objects. Data locality is excellent, which is good for CPU caches.class, record class, arrays, strings). Allocation involves finding free space and is slower than stack allocation. Deallocation is handled by the Garbage Collector (GC), which introduces overhead. Accessing heap objects requires an extra level of indirection (following the reference), which can slightly impact performance and cache efficiency.Because struct and record struct instances are often allocated on the stack, they avoid the overhead associated with heap allocation and garbage collection, making them inherently faster for creation and destruction, especially for short-lived, small objects.
Illustration of how value types reside on the stack while reference types have a reference on the stack pointing to data on the heap.
The GC periodically scans the heap to find objects that are no longer referenced and reclaims their memory. While crucial for automatic memory management, GC cycles consume CPU time and can cause application pauses, especially during intensive allocation scenarios common with classes and record classes. Reducing heap allocations by using structs or record structs where appropriate is a key performance optimization strategy in C#.
While stack allocation is fast, value types (struct, record struct) have a potential performance downside: they are copied by value. When you assign a struct to a new variable or pass it as a method argument (without ref or in modifiers), the entire data content is duplicated. For small structs (e.g., less than 16 bytes, though the exact threshold varies), this copying is usually negligible and outweighed by the benefits of stack allocation. However, for very large structs, the cost of copying can become significant and potentially make them slower than using a reference type (class) where only the reference is copied.
While theoretical differences point towards structs and record structs being faster, benchmarks provide concrete evidence. Numerous performance tests using tools like BenchmarkDotNet consistently show patterns:
record struct as potentially the fastest option, especially when value-based equality and immutability are needed. Some tests show record structs can be considerably faster (even up to 20x in specific scenarios like equality checks) than record class and sometimes even slightly faster than plain struct due to optimized compiler-generated code.This chart provides a generalized, relative comparison of the performance characteristics. Higher scores indicate better performance or lower negative impact (e.g., higher GC Impact score means *less* negative impact). Note that these are relative and subjective scores for illustrative purposes; actual performance depends heavily on the specific workload.
Records (record class and record struct) automatically provide implementations for value-based equality. This means two record instances are considered equal if their public properties have the same values. While convenient, performing value-based equality can be more computationally expensive than the default reference equality check used by classes (which just compares memory addresses). However, the compiler-generated equality methods for records are often highly optimized, especially for record struct, potentially outperforming manual implementations or reflection-based comparisons.
Records encourage immutability (data cannot be changed after creation). While not a direct speed boost in single-threaded scenarios, immutability simplifies program logic, especially in concurrent or multi-threaded applications. It eliminates entire classes of bugs related to shared mutable state and can reduce the need for defensive copying or locking, indirectly contributing to better overall system performance and robustness.
This mind map summarizes the key distinctions and relationships between Classes, Structs, and Records in C# concerning their type, memory management, and core characteristics.
This table provides a concise summary of the key differences relevant to performance:
| Feature | Class | Struct | Record Class | Record Struct |
|---|---|---|---|---|
| Type Kind | Reference Type | Value Type | Reference Type | Value Type |
| Memory Location | Heap | Stack (typically) / Inline | Heap | Stack (typically) / Inline |
| Allocation Overhead | High (Heap + GC) | Low (Stack) | High (Heap + GC) | Low (Stack) |
| Garbage Collection Impact | Yes | No (directly, if on stack) | Yes | No (directly, if on stack) |
| Assignment/Passing | Copies Reference | Copies Data | Copies Reference | Copies Data |
| Copying Cost | Low (Reference) | Potentially High (Large Structs) | Low (Reference) | Potentially High (Large Structs) |
| Default Equality | Reference-Based | Memberwise Value-Based | Value-Based (Compiler-Generated) | Value-Based (Compiler-Generated, Optimized) |
| Immutability | Manual Implementation | Manual Implementation | Built-in Support (Default) | Built-in Support (Default) |
| Inheritance | Yes | No (from other structs/classes) | Yes (from other classes/record classes) | No (from other structs/classes) |
| General Performance Rank (Fastest First) | 4th | 2nd | 3rd | 1st |
| Best Use Case (Performance Focus) | Large, complex, mutable objects; Polymorphism needed | Small, frequently used, immutable data; Minimize GC | Immutable data objects where reference semantics are needed/acceptable | Small, immutable data objects; Maximize performance & minimize GC; Value semantics needed |
For a visual and verbal explanation of the differences between these C# types, including when to use each, check out this helpful video:
This video covers the fundamental concepts of classes, structs, record classes, and record structs, providing context that complements the performance discussion.
While benchmarks often show record struct > struct > record class > class in terms of raw speed for allocation and access, choosing solely based on performance can lead to poor design. Consider these guidelines:
class or record class when:
struct or record struct when:
Point, Color, ComplexNumber).record struct makes this easy).struct and record struct: Choose record struct if you benefit from built-in immutability features and optimized value-based equality comparisons with a concise syntax. Choose struct if you need fine-grained control over mutability or don't need the record-specific features.class and record class: Choose record class when you need a reference type primarily for carrying immutable data and want built-in value equality and with expression support. Choose class for traditional OOP scenarios involving behavior, mutable state, and identity.Always measure! If performance is critical, use profiling tools and benchmarks like BenchmarkDotNet within your specific application context to validate your choices.