Structs

Structs #

Structs and classes are different in certain aspects:

  • they are value types
  • they do not support inheritance
  • they cannot contain finalizers

Other than that, structs can have the same members as a class.

Struct or Class #

We should use structs if we care about value-type semantics. This is particularly beneficial when the object is small (<= 64 bytes), as the value will be copied in at most a few processor cycles. Typically, structs are a better fit for objects with a short lifecycle, e.g., mathematical types that are frequently created and quickly destroyed when using operators - the stack is better for this. If we need a large number of objects, it’s worth remembering that an array of structs will create these objects in a single allocation in a contiguous block of memory. Access to contiguous memory is much faster.

Classes, on the other hand, should be chosen for types that have an ‘identity’, e.g., Customer, Order, Window, and should not be accidentally copied. For large objects (> 64 bytes), copying a reference is much more efficient (almost always in 1 cycle). Objects with a longer lifecycle are usually better suited for storage on the heap, which classes guarantee. If we need to model a hierarchy of objects that require inheritance, we must use classes.

Default Constructor #

Structs always have a parameterless default constructor that zeroes out all the struct’s fields. Even if we define a parameterless constructor, we can call the default constructor via default. Furthermore, an array of structs is bitwise-zeroed by default, and classes also bitwise-zero their fields. It’s worth protecting against this and always treating a zeroed-out struct as a possible valid value.

Point p1 = new Point(1); // 1, 1
Point p2 = new Point();  // 0, 0

public struct Point
{
    public float X { get; set; }
    public float Y { get; set; } = 1;
    
    public Point(float x) => X = x;
}

readonly structs #

The readonly keyword in a struct definition enforces that the struct is immutable, meaning its fields must also be read-only, and properties can only have a get accessor.

public readonly struct Point
{
    public readonly float X;
    public float Y { get; } = 1;
    
    public Point(float x) => X = x;
}

Individual methods can also be marked as readonly, which prevents them from modifying the struct’s fields.

ref structs #

Structs with the ref keyword can only be allocated on the stack. Any construct that would force such a variable to be placed on the heap results in a compilation error. Among other things, you cannot create arrays of such structs, they cannot be fields of classes or non-ref structs, they cannot be used inside iterators, asynchronous methods, or lambdas, and they cannot implement interfaces.

public ref struct Point
{
    public float X { get; set; }
    public float Y { get; set; }
}