Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A struct in C# is a user-defined value type that encapsulates data and related functionality. Unlike classes, which are reference types allocated on the managed heap, structs are value types. Their memory is typically allocated on the stack or inline within containing types, though they can reside on the heap under specific conditions, such as when boxed, captured by closures, or hoisted into state machines.

Syntax Visualization

public struct Vector2D
{
    public double X;
    public double Y;

    // Parameterized constructor
    public Vector2D(double x, double y)
    {
        X = x;
        Y = y;
    }

    public void Offset(double dx, double dy)
    {
        X += dx;
        Y += dy;
    }
}

Memory Allocation and Assignment Semantics

Because structs are value types, variables of a struct type directly contain their data.
  • Allocation Context: Local struct variables are typically allocated on the stack. However, they are allocated on the heap if they are captured by closures (lambdas or local functions) or hoisted into compiler-generated state machines (such as in async methods or iterator blocks). When declared as a field within a class, a struct is allocated inline on the heap as part of the class instance’s memory layout.
  • Pass-by-Value: When a struct is assigned to a new variable or passed as a method argument, the runtime creates a complete bitwise copy of the payload. Modifications to the copy do not affect the original instance unless passed by reference using the ref or out modifiers. (The in modifier passes a variable by readonly reference, meaning the parameter cannot be modified).
  • Boxing: If a struct is cast to an interface it implements or to System.Object, it undergoes boxing. The runtime allocates a wrapper object on the heap and copies the struct’s value into it, incurring performance overhead.

Inheritance and Type Hierarchy

Structs have strict inheritance constraints:
  • They implicitly inherit from System.ValueType, which in turn inherits from System.Object.
  • They are implicitly sealed. A struct cannot be inherited by another struct or class, nor can it inherit from any type other than System.ValueType.
  • They can implement one or multiple interfaces.

Constructors and Initialization

  • Default State: Structs inherently possess a default state where all value-type fields are zeroed and reference-type fields are null. This is achieved via the default keyword or the implicit parameterless constructor.
  • Parameterless Constructors: As of C# 10, structs can declare explicit parameterless constructors. As of C# 11, the definite assignment requirement was removed; any unassigned fields in a struct constructor are automatically initialized to their default values.
  • Field Initialization: Structs support field initializers (e.g., public int X = 5;), but the compiler implements this by synthesizing a parameterless constructor.
  • Finalizers: Structs cannot declare finalizers (destructors) because they are not subject to garbage collection.

readonly struct

Applying the readonly modifier to a struct declaration enforces immutability at the compiler level.
public readonly struct ImmutableVector
{
    public double X { get; }
    public double Y { get; }

    public ImmutableVector(double x, double y)
    {
        X = x;
        Y = y;
    }
}
In a readonly struct, all fields must be marked readonly, and auto-implemented properties must be get-only. This modifier allows the compiler to optimize performance by eliminating defensive copies when the struct is passed as an in parameter or accessed as a readonly field.

ref struct

A ref struct is a specialized value type designed strictly for stack allocation.
public ref struct StackOnlyBuffer
{
    public Span<byte> Buffer;
}
To guarantee they never escape to the heap, ref struct types are subject to severe compiler restrictions:
  • They cannot be boxed.
  • They cannot be assigned to variables of type object, dynamic, or any interface type.
  • They cannot be fields of a class or a non-ref struct.
  • As of C# 13, they can be used in asynchronous methods (async/await) or iterator blocks (yield return), but they strictly cannot cross await or yield boundaries, as doing so would require hoisting them into a heap-allocated state machine.
Master C# with Deep Grasping Methodology!Learn More