Back to Blog
C#

C# Required vs Init: Key Differences

c# required vs init: Understand the differences between C# required and init, how they enforce object initialization, and when to use each for clean API design.

C#required modifierinit accessorobject initializationimmutability
Comparison of C# required and init modifiers showing compile-time enforcement and immutability

When you need to enforce that a property is set during object creation in C#, you have two primary tools: the required modifier and the init accessor. The choice between c# required vs init affects compile-time validation, immutability, and how consumers construct your objects. Understanding the exact behavior of each is essential for designing clean, predictable APIs.

What Does the required Modifier Enforce?

The required modifier, introduced in C# 11, forces the compiler to require that a property or field be initialized during object construction. This enforcement happens at compile time, meaning the code will not build if the property is not set. The initialization can occur either in a constructor or via an object initializer.

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

With this definition, any code that creates a Product must set Name and Price. The following will not compile:

var product = new Product(); // Error: required member 'Product.Name' must be set

The required modifier is useful when you want to guarantee that certain data is always present after construction. It shifts the responsibility from runtime checks to the compiler, reducing the chance of NullReferenceException or missing-value bugs.

What Does the init Accessor Allow?

The init accessor, introduced in C# 9, defines a property that can be set only during object initialization. After the object is constructed, the property becomes read-only. This is a key building block for immutable objects.

public class Order { public string OrderId { get; init; } public DateTime CreatedAt { get; init; } }

An init-only property can be assigned in an object initializer or in a constructor, but not after the object has been created. For example:

var order = new Order { OrderId = "A100", CreatedAt = DateTime.UtcNow }; order.OrderId = "B200"; // Error: property is read-only after initialization

The init accessor gives you immutability without requiring a full constructor with parameters. It works naturally with object initializers, making the code more readable when many properties need to be set.

Comparing required and init in Practice

While required and init are often used together, they solve different problems. required enforces that a property is set; init enforces that it cannot be changed after construction. The table below summarizes the key differences:

Aspectrequiredinit
EnforcementCompile-time: must be setCompile-time: cannot be set later
ImmutabilityDoes not imply immutabilityMakes property read-only after init
Typical usageMandatory dataImmutable data
Allowed assignmentConstructor or object initializerConstructor or object initializer
C# versionC# 11C# 9

You can use required without init to have a mutable property that must be initialized, or init without required to allow optional initialization. The two are orthogonal.

Combining required with init for Immutable Objects

A common pattern is to combine required and init to create immutable objects that guarantee all necessary fields are present. This gives you both compile-time validation and immutability.

public class Customer { public required string CustomerId { get; init; } public required string Email { get; init; } public string? DisplayName { get; init; } }

Here, CustomerId and Email must be set during initialization, and once set, they cannot change. DisplayName is optional but also immutable. This pattern is ideal for DTOs, configuration records, and value objects where the data should not change after creation.

When you combine these modifiers, the object initializer syntax becomes the primary construction mechanism. This is particularly useful when you have many properties, as it avoids long constructor parameter lists and makes the code self-documenting.

Constructor Parameters vs Object Initializers

Both required and init can be used with constructors, but the interaction differs. With a constructor, you can assign required properties directly, and you can also assign init properties if the constructor itself is the initializer. However, using object initializers is often cleaner when you have many properties.

Consider a class that uses a constructor to enforce required data:

public class Invoice { public required string InvoiceNumber { get; init; } public required decimal Total { get; init; } public Invoice(string invoiceNumber, decimal total) { InvoiceNumber = invoiceNumber; Total = total; } }

In this case, the constructor enforces the required properties, but the init accessor still prevents modification after construction. If you omit the constructor, you can rely on the object initializer to set the required members. The choice depends on whether you want to centralize validation logic in a constructor or keep the construction flexible.

Object initializers are more readable when the number of properties is large, and they allow the caller to set only the properties they care about. Constructors are better when you need to perform additional logic during construction, such as validation or derived value computation.

Edge Cases: Inheritance, Serialization, and Anonymous Types

The required modifier has specific behavior with inheritance. A derived class must also mark the property as required if it wants to keep the requirement. For example:

public class Base { public required string Name { get; set; } } public class Derived : Base { // Name is inherited, but still required }

If a derived class hides a required property, the compiler may issue warnings or errors depending on the exact declaration. This is a subtle area that requires careful attention when designing class hierarchies.

Serialization frameworks also interact with these modifiers. JSON deserializers like System.Text.Json can set init properties during deserialization because they use the object initializer pattern internally. However, required properties must be present in the JSON payload; otherwise, the deserializer may throw an exception. This can be a breaking change when adding required to an existing model that is consumed by older clients.

Anonymous types are always immutable and use init-like behavior, but they do not support required. If you need to enforce required members in a lightweight structure, consider using a record with required properties instead.

Choosing Between required and init for API Design

When designing a public API, the decision between required and init should be guided by the contract you want to establish with callers. Use required when a property is logically necessary for the object to function correctly. Use init when you want to prevent mutation after creation, even if the property is optional.

The combination of both is powerful for domain models and DTOs. It makes the object's invariants explicit at compile time and prevents accidental mutation later. However, be mindful of backward compatibility. Adding required to an existing property is a breaking change because existing code that constructs the object without setting that property will no longer compile. Adding init to a property that was previously settable is also breaking, as it removes the setter.

For internal code, the choice is less critical, but it still affects maintainability. Using required and init together reduces the number of runtime null checks and makes the codebase more predictable. The compiler acts as a guard, catching missing assignments early in the development cycle.

Runtime Behavior and Performance

Neither required nor init introduces runtime overhead. They are compile-time constructs that produce the same IL as regular property assignments. The only runtime difference is that init properties are backed by a read-only field if you use a field-backed property, but the accessor itself is just a method call. In practice, there is no measurable performance impact.

The real benefit is in code quality and reliability. By moving initialization requirements to compile time, you eliminate a class of runtime errors that would otherwise surface as null reference exceptions or invalid object states. This is especially valuable in large codebases where objects are constructed in many places and the cost of a missing property can be high.

When using reflection or dynamic code, the compiler checks do not apply. For example, if you use Activator.CreateInstance to create an object without setting required properties, you will get an instance with null or default values. This is a known limitation; required is only enforced for statically compiled code. If you rely on reflection-based construction, you may need to add runtime validation as a fallback.

Final Code Example: A Practical Use Case

To illustrate the recommended pattern, consider a configuration object that must have a connection string and an API key, and should be immutable after loading:

public class AppConfig { public required string ConnectionString { get; init; } public required string ApiKey { get; init; } public int TimeoutSeconds { get; init; } = 30; }

This class enforces that both ConnectionString and ApiKey are provided at construction, and they cannot be changed afterward. The TimeoutSeconds property has a default value and is optional. Consumers can create an instance like this:

var config = new AppConfig { ConnectionString = "Server=...", ApiKey = "abc123" };

If a developer forgets to set ApiKey, the code will not compile. This is the core advantage of combining required and init: the compiler enforces the contract, and the object remains immutable. The pattern scales well to large models and is a recommended approach for modern C# APIs.

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