Required Members: Compile-Time Guarantees for Object Initialisation
Object initialisers have been in C# since version 3, but they have always had a blind spot: nothing forces a caller to set the properties that matter. You could create a Person with a name but no email, or a Configuration missing its connection string, and the compiler would not complain. C# 11's required modifier finally closes that gap.
The Problem
Consider a simple DTO:
public class UserRegistration
{
public string Email { get; set; }
public string DisplayName { get; set; }
public string? Bio { get; set; }
}
Nothing prevents this:
var registration = new UserRegistration
{
Bio = "Hello world"
// Email and DisplayName silently default to null
};
The traditional fix was to add a constructor with required parameters, but that defeats the purpose of object initialisers and creates friction when you have many properties with a mix of required and optional values.
The Solution
The required modifier marks properties and fields that must be set by the caller via an object initialiser or constructor:
public class UserRegistration
{
public required string Email { get; set; }
public required string DisplayName { get; set; }
public string? Bio { get; set; }
}
Now the compiler enforces it:
// Error CS9035: Required member 'Email' must be set
var registration = new UserRegistration
{
DisplayName = "Alice"
};
// This compiles
var registration = new UserRegistration
{
Email = "[email protected]",
DisplayName = "Alice"
};
Required Members and Constructors
If you have a constructor that sets the required properties, you can mark it with [SetsRequiredMembers] to tell the compiler that callers using that constructor do not need to set them again:
public class UserRegistration
{
public required string Email { get; set; }
public required string DisplayName { get; set; }
public string? Bio { get; set; }
public UserRegistration() { }
[SetsRequiredMembers]
public UserRegistration(string email, string displayName)
{
Email = email;
DisplayName = displayName;
}
}
// Both of these compile:
var a = new UserRegistration("[email protected]", "Alice");
var b = new UserRegistration
{
Email = "[email protected]",
DisplayName = "Alice"
};
Note that [SetsRequiredMembers] is a trust-based attribute — the compiler does not verify that the constructor actually assigns the members. It is your responsibility to ensure correctness.
Inheritance
Required members compose well with inheritance. A derived class can add its own required members, and all required members from the entire hierarchy must be set:
public class Entity
{
public required Guid Id { get; set; }
}
public class AuditableEntity : Entity
{
public required DateTime CreatedAt { get; set; }
public required string CreatedBy { get; set; }
}
var entity = new AuditableEntity
{
Id = Guid.NewGuid(),
CreatedAt = DateTime.UtcNow,
CreatedBy = "system"
};
Omitting any of Id, CreatedAt, or CreatedBy produces a compile-time error.
Required Members on Records
Required members work with records, giving you the best of both worlds — positional syntax for some parameters and required initialisers for others:
public record Product(string Sku)
{
public required string Name { get; init; }
public required decimal Price { get; init; }
public string? Description { get; init; }
}
var product = new Product("SKU-001")
{
Name = "Widget",
Price = 9.99m
};
Practical Patterns
Required members work particularly well for configuration and options classes:
public class SmtpOptions
{
public required string Host { get; set; }
public required int Port { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public bool UseTls { get; set; } = true;
}
This makes it impossible to forget the host or port whilst keeping optional settings flexible. It is a pattern that self-documents which values are essential.
Limitations
There are a few things to be aware of. Required members cannot be used with positional record parameters — those are already required by the constructor. The [SetsRequiredMembers] attribute is all-or-nothing; you cannot indicate that a constructor sets only some required members. And serialisation frameworks need to be aware of required members — System.Text.Json handles them correctly from .NET 7 onwards, but third-party libraries may need updates.
When to Adopt
If you have types where certain properties must always be set and you prefer object initialisers over constructors, required is a straightforward improvement. It turns runtime NullReferenceException surprises into compile-time errors, which is always a worthwhile trade.