Hello! Welcome to the third lesson in your C# mastery course.
In our previous lesson, we investigated boxing and unboxing, uncovering the performance cost associated with heap allocations and the resulting pressure on the Garbage Collector (GC). This understanding sets the stage perfectly for today's topic.
This lesson addresses the learning outcome: Define and use structs, including when to prefer structs over classes for performance.
We will explore struct as a primary tool for writing high-performance C# by controlling memory allocation. Given your goal to understand the "under the hood" mechanics, we'll go beyond simple syntax to analyze the trade-offs between struct and class, focusing on memory layout, copy semantics, and CPU cache efficiency.
1. Structs vs. Classes: The Fundamental Difference
As we've discussed, the most crucial distinction in the .NET type system is between value types and reference types. A struct is a value type, while a class is a reference type. This single difference dictates their behavior in memory.
- Classes (Reference Types): When you create an instance of a class, the object's data is allocated on the heap. The variable you use holds only a reference (a memory address) to that object.
- Structs (Value Types): A struct's data is stored in-place. If it's a local variable in a method, it's typically allocated on the stack. If it's a field within a class, it's allocated inline as part of that class's object on the heap.
This image provides a clear visual of this distinction.

This difference in memory allocation leads to two critical behavioral changes:
- Assignment: Assigning a class variable copies the reference. Both variables then point to the same object. Assigning a struct variable copies the entire value. Each variable holds an independent copy of the data.
- Method Arguments: When you pass a class to a method, the reference is copied (pass-by-reference-value). When you pass a struct, the entire value is copied (pass-by-value).
To see these concepts in action, the following video provides an excellent demonstration.
The differences between classes and structs in C#
The video 'The differences between classes and structs in C#' by Scott Steffes clearly explains and visualizes these core differences.
Please watch from the beginning to 04:28. The video will cover: How structs and classes are allocated in memory (stack, heap, inline). The difference in behavior when you assign one variable to another. How passing a struct to a function creates a copy, unlike with a class.
2. When to Prefer Structs for Performance
Now we arrive at the central question: why choose a struct? The answer almost always revolves around performance, particularly in "hot paths" of your code where many objects are created and destroyed.
Your previous work with ASP.NET 4 gives you a good context for where performance matters. While you might not use structs for high-level business objects, they are critical in lower-level libraries, data processing pipelines, or any code that runs in a tight loop.
The Upside: Reducing GC Pressure and Improving Locality
As you saw in the last lesson on boxing, heap allocations have a cost, primarily the downstream work they create for the Garbage Collector. Structs help in two main ways:
- Fewer Allocations: By allocating on the stack for local variables, structs avoid creating "garbage" on the heap. For short-lived objects created in a loop, this can dramatically reduce GC pressure, leading to fewer and shorter pauses in your application.
- Better Cache Locality: When you have an array of structs, e.g.,
Point[], the data for all the points is stored in one contiguous block of memory. When your code iterates through this array, the CPU can efficiently load this data into its cache. An array of classes,PointClass[], is actually an array of references, and the actual objects can be scattered all over the heap. Iterating through them can cause many "cache misses," which significantly slows down CPU-intensive processing.
The Downside: The Cost of Copying
The value semantics of structs are not a free lunch. Since the entire struct is copied on assignment or when passed to a method, large structs can be expensive. Copying a 100-byte struct is much slower than copying an 8-byte reference to a class.
The following video segments build on the previous one to explore these performance trade-offs in detail.
The differences between classes and structs in C#
Let's continue with the Scott Steffes video and add a clip from Nick Chapsas to get a balanced view on the performance implications.
First, watch from 04:28 to 07:10 in the Scott Steffes video. He explains the performance benefits related to the GC and CPU cache, and also introduces the potential downside of copying.
When to Use Classes and Structs in .NET
Now, let's watch a segment from Nick Chapsas that really emphasizes the cost of copying large structs and introduces the official Microsoft guidance.
Watch from 04:56 to 07:57. Focus on the discussion about the performance hit from copying large structs and the general guideline to keep them small (e.g., under 16 bytes).
3. Best Practices for Defining and Using Structs
You now have the core theory: structs avoid GC pressure but have a copy cost. This leads to clear design guidelines, which are essential for any developer aiming for mastery.
The following article is an expert-level guide that consolidates these concepts and introduces modern best practices, like readonly struct. Since you want to understand the deep mechanics, this detailed resource is invaluable.
Choosing Between Classes, Records, and Structs in C# – BigO ...
The article 'Choosing Between Classes, Records, and Structs in C#' by Omar Besiso offers a definitive guide. We'll focus on the sections that detail struct usage and best practices.
This is a detailed article, so please focus your reading on these key sections: "Structs – Value Types in C#": Read this entire section. It provides a comprehensive overview of struct semantics, constructors, mutability, and memory/performance characteristics. Pay close attention to the pitfall of mutable structs. "Readonly Structs – Enforcing Immutability": Read this short section to understand why readonly struct is the modern best practice. "Example 4: Mutable Struct Pitfall": This example clearly illustrates the dangerous and confusing behavior of mutable structs when exposed via properties. "Best Practices and Decision Guidelines": Read this numbered list of questions. It provides an excellent framework for deciding when to use a struct.
Synthesis: Defining an Ideal Struct
Based on these resources, let's synthesize the ideal use case and definition for a struct.
The official .NET guideline is a great starting point: a type should be a struct if it logically represents a single value, is small (under 16 bytes), is immutable, and will not be frequently boxed.
A perfect example is a 2D point.
// Good: Small, represents a single value, and is immutable.
public readonly struct Point
{
public double X { get; }
public double Y { get; }
public Point(double x, double y)
{
X = x;
Y = y;
}
}
Key features of this readonly struct:
readonly struct: This modifier enforces immutability. The compiler will prevent any instance fields from being modified outside of the constructor. This avoids the "mutable struct pitfall" entirely and allows the compiler to make optimizations (like avoiding defensive copies).readonlyproperties: Theget;only auto-properties (orinit;in newer C# versions) ensure the state cannot be changed after construction.- Small Size: This struct contains two
doublefields (8 bytes each), for a total of 16 bytes. This fits perfectly within the performance guidelines.
Contrast this with a Customer object. A customer has an identity (two customers with the same name are still different people), its state might change over time, and it's likely larger than 16 bytes. A Customer should always be a class.
Conclusion
This lesson provided a deep dive into C# structs, moving from their fundamental memory model to the nuanced performance trade-offs that guide their use.
Key Takeaways:
- Structs are value types: They are allocated on the stack or inline, and their value is copied on assignment.
- Classes are reference types: They are allocated on the heap, and their reference is copied on assignment.
- Prefer structs for performance when:
- The type is small (e.g., < 16 bytes).
- It represents a single, logical value (like a coordinate or a measurement).
- It is immutable.
- You will be creating many short-lived instances, and you want to reduce GC pressure.
- Always prefer
readonly structto enforce immutability and prevent common bugs. Avoid mutable structs unless you are in a highly specialized performance scenario and fully understand the consequences.
Preview of the Next Lesson:
We've established that structs are value types and cannot be null. But what happens when you need to represent a "missing" or "not applicable" value for a type like int or DateTime? In our next lesson, we will explore nullable value types, learning how to use Nullable<T> and the ? syntax to solve this common problem.