Back to Blog
C#

C# Primary Constructor Struct: Syntax and Behavior

c# primary constructor struct: Understand C# primary constructor struct syntax, how parameters map to fields, and when to use them.

C#StructsPrimary ConstructorsC# 12Object Initialization.NET
Illustration of a C# struct with primary constructor parameters mapping to fields.

c# primary constructor struct requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

C# 12 introduced primary constructors for both classes and structs. For structs, the feature changes how you declare and initialize state, but it also brings a few constraints that matter in practice. This article explains the syntax, how the compiler handles parameters, and where primary constructors on structs can cause surprises.

Primary Constructor Syntax for Structs

A primary constructor is declared directly on the type name. For a struct, the parameters are in scope throughout the struct body, including in field initializers and property initializers.

public readonly struct Point(double x, double y) { public double X { get; } = x; public double Y { get; } = y; }

Here, x and y are captured by the compiler and used to initialize the auto-properties. The struct still has an implicit parameterless constructor, and the primary constructor is called only when you use new Point(...). If you do not use the parameters in any member, the compiler warns that they are unused.

How Struct Primary Constructors Store Parameters

Unlike classes, structs cannot have a primary constructor that simply stores parameters as hidden fields. For a class, the compiler generates a backing field for each primary constructor parameter if it is used in a member. For a struct, the compiler does not generate hidden fields for the parameters themselves; it only uses them to initialize members you define. This means you must explicitly assign the parameter values to properties or fields.

public struct Temperature(double celsius) { public double Celsius { get; } = celsius; public double Fahrenheit => Celsius * 9 / 5 + 32; }

If you forget to use a parameter, it is simply unused. The struct still has a default parameterless constructor that initializes all fields to their default values, which can lead to an instance where the primary constructor logic never runs.

Validation and Initialization Logic

You can add a constructor body to the primary constructor to run validation or other setup. The body executes after all field and property initializers.

public readonly struct Time(int hour, int minute) { public int Hour { get; } = hour; public int Minute { get; } = minute; public Time(int hour, int minute) : this(hour, minute) { if (hour < 0 || hour > 23) throw new ArgumentOutOfRangeException(nameof(hour)); if (minute < 0 || minute > 59) throw new ArgumentOutOfRangeException(nameof(minute)); } }

Note that the primary constructor body is not the same as the explicit constructor. If you define an explicit constructor, you must chain to the primary constructor with : this(...). The primary constructor body runs before the explicit constructor body, which allows you to enforce invariants consistently.

Struct Copy Semantics and Primary Constructors

Structs are value types, and copying is a common operation. When you copy a struct that uses a primary constructor, the copy contains the same member values. There is no special copy behavior tied to the primary constructor. However, if your struct has a readonly property initialized from a primary constructor parameter, that property remains readonly in the copy.

var p1 = new Point(3, 4); var p2 = p1; // p2.X == 3, p2.Y == 4

This is straightforward, but it becomes subtle when the struct contains a reference type member. The copy shares the reference, which can lead to unintended mutation if the referenced object is mutable. This is not specific to primary constructors, but it is a reminder that structs should be designed with immutable or value-type members to avoid aliasing bugs.

Compatibility and Limitations

Primary constructors for structs are available starting with C# 12. If you are on an older compiler, this syntax will not compile. Also, a struct with a primary constructor cannot have a parameterless constructor that you define explicitly. The compiler always provides a default parameterless constructor that sets all fields to default. This can be surprising if you expect the primary constructor to be called when you do new MyStruct().

Another limitation is that primary constructor parameters cannot be used in ref or out contexts, and they cannot be passed to base constructors (structs do not have base classes). For readonly structs, the primary constructor parameters are treated as readonly, so you cannot assign to them after initialization.

When to Use a Primary Constructor on a Struct

Primary constructors are most useful for simple data-carrying structs where the constructor's only job is to initialize properties from the parameters. They reduce boilerplate and make the intent clear.

public readonly record struct Money(decimal Amount, string Currency);

If your struct requires complex validation, multiple constructors, or lazy initialization, a traditional constructor may be clearer. Primary constructors also do not support optional parameters in a way that differs from regular constructors; you still need to provide all arguments unless you define overloads.

Use a primary constructor when the struct is a simple value container with one natural way to construct it. Avoid it when you need to maintain backward compatibility with an existing parameterless constructor or when the constructor body would become large enough to obscure the fields.

Common Mistakes and How to Avoid Them

A frequent mistake is assuming that the primary constructor is invoked when you use the default parameterless constructor. It is not. The default constructor always exists and initializes all fields to their default values. If your struct has a primary constructor, you must explicitly call new MyStruct(...) to get the initialized values.

Another mistake is forgetting to assign all fields in the primary constructor body. Because structs require all fields to be definitely assigned before use, the compiler will enforce that any field you declare is initialized either in the primary constructor body or in a field initializer. If you add a new field and forget to initialize it, you will get a compile error.

Finally, be careful with mutable structs that have primary constructors. If you expose a property that can be set after construction, the primary constructor becomes just an initializer, and the struct's value semantics can lead to confusing behavior when copied. Prefer immutable structs, especially when using primary constructors, to keep the behavior predictable.

Struct Primary Constructor and Performance

Primary constructors do not introduce a performance penalty beyond what a regular constructor would. The compiler generates the same IL for the constructor body. However, the convenience of primary constructors can encourage more struct usage, which can reduce heap allocations compared to classes. For hot paths, the lack of allocation is beneficial, but you should still measure if performance is critical. The main performance consideration is not the primary constructor itself but how the struct is used—passing large structs by value copies them, so keep structs small.

For a readonly struct with primary constructor parameters, the compiler can optimize property access because the properties are get-only. This can enable better inlining and reduce method call overhead. But these are micro-optimizations; the real gain is in avoiding heap allocation when a class would otherwise be used.

Where Primary Constructors Fall Short

Primary constructors on structs do not support primary constructor parameter destructuring or pattern matching directly. You cannot use the parameter names as properties unless you explicitly define them. Also, you cannot use primary constructor parameters in nameof expressions because they are not members. If you need to reflect on the constructor parameters, a traditional constructor with explicit parameters is more discoverable.

Another gap is that primary constructors cannot be used with structs that have a ref field or a Span<T> field that requires a ref struct. The ref struct restriction applies, and primary constructors do not change that. If you need a ref struct with initialization logic, you still need a traditional constructor.

Despite these limitations, primary constructors are a clean way to define small data structs. They fit well with records and positional patterns, and they reduce the ceremony of declaring a separate constructor for a simple type. As with any language feature, the decision to use them should be based on clarity and maintainability, not just novelty.

c# primary constructor struct: Practical Usage and Code Exam | RYUSLOG DEV