Back to Blog
C#

C# Required Property: Enforcing Initialization

c# required property: Learn how the C# required modifier enforces property initialization at compile time, its interaction with constructors, and practical implication...

C# required modifierobject initializersconstructor designcompile-time validation.NET 7
A C# code snippet showing a required property declaration with a compiler check icon indicating compile-time enforcement.

When a class has properties that must be set for the object to be valid, you previously had to rely on constructors or manual validation. The required modifier, introduced in C# 11, moves that check to compile time, making it impossible to create an instance without providing those values. This article explains how the c# required property feature works, where it applies, and what it means for constructors, serialization, and maintainability.

What the required Modifier Enforces

The required modifier is a compile-time contract. When you mark a field or property with required, the compiler ensures that every construction path of the type explicitly sets that member before the object is fully created. This applies to object initializers, constructors, and factory methods that call constructors. The enforcement is purely static: there is no runtime check or reflection-based validation. If a developer tries to instantiate the class without setting the required member, the code does not compile.

This behavior closes a gap where objects could be created in an incomplete state. Previously, you might have written a constructor that accepted all mandatory values, but that approach becomes verbose when a class has many required fields. The required modifier lets you keep a parameterless constructor while still guaranteeing that certain properties are populated.

Declaring a Required Property

To declare a required property, place the required modifier before the property type. The property must be at least as accessible as the containing type, and it cannot be static or init-only in a way that conflicts with the requirement. Here is a minimal example:

public class Product { public required string Name { get; set; } public required decimal Price { get; set; } public string? Description { get; set; } }

With this declaration, the following code compiles because both required properties are set in the object initializer:

var product = new Product { Name = "Laptop", Price = 999.99m };

But this code fails to compile because Price is missing:

var product = new Product { Name = "Laptop" };

The compiler error is clear: Required member 'Product.Price' must be set in the object initializer or attribute constructor. This error appears at compile time, preventing the creation of incomplete objects.

How Required Properties Interact with Constructors

If a class has a constructor, the compiler treats it as a construction path. Every constructor must either set all required properties or delegate to another constructor that does. If a constructor does not set a required property, you must call it with an object initializer that sets the missing property. For example:

public class Order { public required string OrderId { get; set; } public required DateTime CreatedAt { get; set; } public Order() { } public Order(string orderId) { OrderId = orderId; CreatedAt = DateTime.UtcNow; } }

The parameterless constructor is valid because it does not set any required properties; it simply allows the object to be created with an object initializer. The second constructor sets both required properties, so it is also valid. However, if you wrote a constructor that only set OrderId, the compiler would report an error because CreatedAt is not set.

This behavior gives you flexibility. You can have a parameterless constructor for deserialization frameworks, while still enforcing that consumers set required properties via initializers. It also means that any code that uses reflection to create an instance without setting required properties will compile, but the resulting object will be in an invalid state at runtime. The compiler cannot protect against reflection-based creation.

Using SetsRequiredMembers to Bypass Checks

Sometimes you have a constructor that sets all required properties indirectly, perhaps through a helper method or by assigning a default value. In such cases, you can annotate the constructor with [SetsRequiredMembers] to tell the compiler that it fully initializes all required members. This suppresses the compiler's requirement to set them explicitly in the constructor body.

public class Employee { public required string Name { get; set; } public required int Id { get; set; } [SetsRequiredMembers] public Employee(string name, int id) { Name = name; Id = id; } }

Without the attribute, this constructor would produce an error because the required properties are not set before the constructor body ends. With [SetsRequiredMembers], the compiler trusts that the constructor performs the necessary initialization. This attribute is useful when you have a constructor that delegates to a private initializer or when you want to allow derived classes to call a base constructor that sets required members.

It is important to use this attribute only when you are certain that the constructor actually sets every required member. If you misuse it, you reintroduce the possibility of creating an object with missing required values, defeating the purpose of the modifier.

Required Properties and Serialization

Serialization frameworks often need to create objects without calling a constructor that sets all properties. For example, System.Text.Json uses the parameterless constructor when deserializing, then sets properties by name. If a required property is not present in the JSON payload, the deserializer will leave it at its default value, and the object will be in an invalid state. The required modifier does not change this runtime behavior; it only enforces compile-time checks on direct construction.

This means you must decide how to handle missing required properties during deserialization. One approach is to use a custom JsonConverter that validates the presence of required fields. Another is to make the required properties non-nullable and rely on the JSON schema to enforce them, but that does not protect against malformed payloads. For example:

public class ApiRequest { public required string Endpoint { get; set; } public required string Payload { get; set; } }

If a JSON payload lacks Endpoint, the deserializer will create an ApiRequest with Endpoint set to null. Since the property is declared as non-nullable, this can lead to null reference exceptions later. To handle this, you might add a validation step after deserialization or use a constructor that sets defaults and then check for missing values.

The required modifier is not a substitute for runtime validation. It is a developer-facing contract that helps prevent accidental omissions during coding, but it does not protect against data coming from external sources.

Compatibility and Maintainability Considerations

The required modifier is available in C# 11 and later, and it requires the .NET 7 SDK or newer. If you are targeting an older .NET runtime, you can still use the feature if the compiler is recent, but the runtime does not have special support; it is purely a compile-time feature. This means the generated IL is the same as if you had used a normal property. The modifier does not affect binary compatibility.

When adding required to an existing class, consider the impact on code that already constructs the class. Any existing object initializers that omit the newly required property will fail to compile. This is a breaking change, so it should be introduced deliberately. In a large codebase, this can surface many compile errors, which is often the desired outcome because it forces developers to address incomplete initialization.

For library authors, using required can improve the API surface by making it clear which properties are essential. However, it also limits flexibility for consumers who might want to create an object and set properties later. If a property is genuinely optional, do not mark it as required. Reserve the modifier for members that are always needed for the object to function correctly.

Edge Cases: Nullable and Default Values

A required property can be nullable or have a default value. The compiler does not care about the type's nullability or the presence of a default; it only checks that the property is assigned somewhere. For example:

public class Config { public required string? LogPath { get; set; } public required int Timeout { get; set; } = 30; }

Even though LogPath is nullable, it must still be set in an object initializer or constructor. The default value for Timeout does not exempt it from the requirement. This can be surprising. If you want a property to have a default and not require explicit assignment, do not mark it as required. The modifier is about the presence of an assignment, not the final value.

Another edge case is inheritance. When a base class has required properties, derived classes must also set them, either directly or through a base constructor. If a derived class has a constructor that calls a base constructor that sets the required properties, the derived class does not need to repeat the assignments, but the compiler still checks that the base constructor is called. This can lead to complex constructor chains, so it is worth planning how required properties flow through inheritance hierarchies.

Finally, consider the interaction with init accessors. You can combine required with init to create immutable objects that must be fully initialized at creation time:

public class Point { public required int X { get; init; } public required int Y { get; init; } }

This pattern is common for DTOs and value objects. The object cannot be modified after creation, and the compiler ensures that both coordinates are provided. This combination is particularly useful for record types, where init accessors are already the default.

The required modifier is a powerful tool for expressing object invariants at the type level. It shifts the responsibility of complete initialization from runtime checks to the compiler, reducing the chance of errors. When used thoughtfully, it improves code clarity and maintainability, but it requires careful consideration of serialization, inheritance, and existing construction patterns.

c# required property: Practical Usage and Code Examples | RYUSLOG DEV