C# Struct Constructor: Rules and C# 10 Changes
c# struct constructor: Learn C# struct constructor rules: parameterized constructors, implicit parameterless initialization, C# 10 field initializers, and performance.
When you declare a constructor in a C# struct, the rules differ from classes in ways that affect field initialization, runtime behavior, and what you can write depending on your language version. The c# struct constructor must assign every field before it returns, an implicit parameterless constructor always exists, and C# 10 added the ability to define your own parameterless constructor and field initializers. This article covers the syntax, the runtime constraints, and the practical decisions around struct constructors.
Parameterized Struct Constructors and Definite Assignment
A struct can declare a constructor that takes parameters. The constructor must assign every field of the struct before it returns control to the caller.
public struct Point { public int X; public int Y; public Point(int x, int y) { X = x; Y = y; } }
The compiler enforces definite assignment. If a field is left unassigned, the code does not compile:
public struct Point { public int X; public int Y; public Point(int x) { X = x; // CS0171: Field 'Point.Y' must be fully assigned before // control is returned to the caller } }
This rule exists because a struct is a value type. When you write Point p = new Point(3, 4);, the runtime allocates the struct's storage inline, either on the stack or inside the containing object, and the constructor runs against that existing memory. There is no separate heap object that can hold partially initialized state, so the struct's fields must be fully defined when the constructor exits.
The Implicit Parameterless Constructor
Every struct has an implicit parameterless constructor that zero-initializes all fields. This constructor always exists, even when you define parameterized constructors.
Point p = new Point(); // X = 0, Y = 0
The implicit constructor sets every field to its default value: 0 for numeric types, null for reference types, and default values for nested value types. The same zero-initialization applies when you write default(Point) or create an array of structs.
Before C# 10, you could not replace this implicit constructor with your own parameterless constructor. Declaring one produced a compile error. The design reason is consistency: arrays of structs and default expressions create struct instances without running any user code, so allowing a custom parameterless constructor would create two different initialization paths for the same type.
C# 10: Explicit Parameterless Constructors and Field Initializers
C# 10, shipped with .NET 6, relaxed these restrictions. You can now declare an explicit parameterless constructor in a struct, and you can use field initializers.
public struct Point { public int X; public int Y; public Point() { X = 1; Y = 2; } }
When you declare an explicit parameterless constructor and do not assign every field in its body, you must chain to this() so the implicit zero-initialization runs first.
public struct Point { public int X; public int Y; public string Label; public Point() : this() { Label = "origin"; } }
Field initializers follow the same rule. If a struct has field initializers, you must declare an explicit parameterless constructor:
public struct Point { public int X = 10; public int Y = 20; public Point() { } }
The critical difference from classes is that field initializers do not run in every creation path. new Point() runs the explicit constructor and applies the initializers, but default(Point) and new Point[3] produce zero-initialized fields:
var a = new Point(); // X = 10, Y = 20 var b = default(Point); // X = 0, Y = 0 var c = new Point[3]; // every element has X = 0, Y = 0
If your code relies on field initializers to establish invariants, you must remember that default and array allocation bypass them. This is the most common source of bugs when migrating structs to C# 10 field initializers.
readonly Struct Constructors
A readonly struct guarantees that no instance member modifies state after construction. The constructor is the only place where fields and get-only auto-properties can be assigned.
public readonly struct Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } }
The compiler enforces this: in a readonly struct, all instance fields must be readonly, and auto-properties must be get-only. Attempting to declare a settable property or a non-readonly field produces a compile error.
Readonly structs also support with expressions, which copy the struct and apply property changes:
var p1 = new Point(3, 4); var p2 = p1 with { X = 10 }; // p2 = (10, 4)
The with expression relies on the struct's copy behavior and does not call a constructor. This is worth knowing when you design structs with invariants: a with expression can produce a value that no constructor would have allowed.
Performance and Allocation Behavior
Struct constructors behave differently from class constructors at runtime. A local struct is allocated on the stack or inline within its containing object; there is no heap allocation and no garbage collection pressure. The constructor call is a direct call, not a virtual dispatch, so the JIT can often inline it.
The main performance cost to watch is copying. Passing a struct by value copies the entire struct, and returning one copies it again. For small structs, such as a point or a range, this is cheaper than a heap allocation plus pointer indirection. For large structs, the copy cost can exceed the cost of a reference type. A common rule of thumb is to keep structs under roughly 16 bytes, but the actual threshold depends on your data and access patterns.
Boxing is another cost to avoid. Casting a struct to an interface or to object boxes it, which allocates on the heap and copies the struct. This matters when you put structs into non-generic collections or pass them to methods that take object.
The implicit parameterless constructor is effectively a no-op. Zero-initialization is performed by the runtime when storage is allocated, so calling new Point() for a struct without an explicit parameterless constructor does not execute any user code.
Struct Constructor or Static Factory Method
A constructor is the right choice when initialization is simple and the struct is small. For example, a Point or a Money amount with a currency code is naturally constructed with a few parameters.
A static factory method becomes useful when you need validation, multiple creation strategies, or a way to express intent.
public readonly struct Temperature { public double Celsius { get; } private Temperature(double celsius) { Celsius = celsius; } public static Temperature FromCelsius(double value) => new Temperature(value); public static Temperature FromFahrenheit(double value) => new Temperature((value - 32) * 5.0 / 9.0); }
The private constructor prevents callers from constructing a Temperature with an ambiguous unit, and the factory methods make the unit explicit at the call site. This pattern also lets you return a cached or default instance when a calculation produces a known value, though for a small struct the allocation difference is negligible.
Use a constructor when the struct's state is fully described by its parameters and no validation is required. Use a factory method when the caller must choose between semantically different ways to create the same struct, or when invalid input should be rejected before a struct value exists.