Struct Vs Class When To Use Which · Quick recall Q&A
2 min readQuick recall Q&A
- No separate heap allocation
- No object header
- No pointer indirection
- If they are fields of a heap object, they live inside that object
- If they are boxed (cast to object or interface), they go to the heap
- Large structs copied often can hurt performance
When the data is small (≤16 bytes), immutable, frequently created, and benefits from value semantics. Structs reduce GC pressure by living inline and being collected with stack frames. Its data is stored on the stack (for locals), or inline inside another object (as part of that object’s memory). This means
Stack memory is automatically reclaimed when a method returns. No garbage collection is needed for stack-allocated data. This is much cheaper than heap allocation + GC.
public readonly struct Money
{
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
public decimal Amount { get; }
public string Currency { get; }
}
void Calculate()
{
Money price = new Money(100, "USD");
// price is on the stack
} // stack frame is popped → memory reclaimed immediately
⚠️ Structs are not always stack-allocated:
Copies become expensive, especially when passing by value. This can negate performance gains and increase stack usage. Use in/ref parameters or switch to classes if the struct grows.
Boxing copies the struct onto the heap and allocates, defeating the GC benefits. Avoid passing structs to APIs expecting object or non-generic interfaces to prevent boxing.
Starting with C# 10, yes, but they must be public/private and initialize all fields. Historically, structs always had an implicit default constructor. Remember that every struct has a zeroed default state.
Use in (readonly ref) for read-only access, or ref/ref readonly when you need to mutate or avoid copies. This keeps performance predictable for larger structs.
No. Structs are sealed value types that inherit from ValueType. They can implement interfaces but cannot participate in class inheritance hierarchies.
Rarely—they often improve locality. However, large structs embedded in arrays can cause cache misses due to size. Evaluate data layout to ensure structs remain lean.
Use Nullable<T> (Tick?). It wraps the struct with a HasValue flag, allowing null-like semantics without resorting to classes.
Prefer immutable structs to avoid accidental copies followed by mutation. Mutable structs can lead to confusing bugs when copies diverge silently.
They support Deconstruct methods and pattern matching just like classes. This makes them ergonomic for lightweight domain data while still keeping value semantics.