C# 11 Required Members for Classes and Structs

C# 11 introduced the required modifier, a feature designed to enforce proper object initialization for classes and structs. This article provides a clear guide on what required members are, why they were introduced, and how to implement them effectively. By using this keyword, developers can ensure that callers populate critical fields or properties during instantiation, reducing boilerplate constructor code while maintaining type safety and preventing uninitialized state bugs.

What Are Required Members?

The required modifier can be applied to fields and properties within class, struct, and record definitions. When a member is marked as required, any code creating an instance of that type using an object initializer must explicitly set that member.

If the consuming code fails to assign a value to a required member during initialization, the C# compiler issues a compile-time error.

public class User
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public string? MiddleName { get; init; }
}

// Valid instantiation:
var user = new User 
{ 
    FirstName = "Jane", 
    LastName = "Doe" 
};

// Compiler Error CS9035: Required member 'User.LastName' must be set.
var invalidUser = new User 
{ 
    FirstName = "Jane" 
};

Why Were Required Members Introduced?

Before C# 11, developers typically faced two suboptimal approaches for enforcing mandatory data:

  1. Constructors with Parameters: Writing constructors that accept every required field guarantees initialization, but it leads to excessive boilerplate, especially with inheritance, and hampers the readability of object initializers.
  2. Object Initializers with Nullable Checks: Relying purely on standard properties allows for flexible object initializers, but the compiler cannot guarantee that consumers will assign values, potentially violating non-nullable reference types.

The required modifier bridges this gap. It allows developers to use clean object initializer syntax while ensuring that non-nullable and mandatory fields are never omitted.

Rules and Behaviors

1. Visibility Constraints

A required member must be at least as visible as its containing type. For example, a public class cannot have a protected or private required property, because external consumers would be unable to initialize it.

2. Usable with init and set

The required modifier works with both mutable setters (set) and immutable init-only setters (init). It is most commonly paired with init to enforce immutability after creation.

3. Inheritance

When a class derives from a base class with required members, the derived class inherits those requirements. Callers instantiating the derived class must initialize required members from both the base and the derived classes.

The [SetsRequiredMembers] Attribute

In scenarios where a class provides a parameterized constructor that assigns all required members internally, the [SetsRequiredMembers] attribute can be used. This tells the compiler that the constructor takes care of initializing the required properties, exempting the caller from using an object initializer.

using System.Diagnostics.CodeAnalysis;

public class User
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    public User() { }

    [SetsRequiredMembers]
    public User(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}

// Valid: Constructor fulfills the required contract
var userFromConstructor = new User("Jane", "Doe");

// Valid: Object initializer fulfills the required contract
var userFromInitializer = new User { FirstName = "Jane", LastName = "Doe" };

When using [SetsRequiredMembers], any derived class constructor calling that base constructor must also declare [SetsRequiredMembers] to maintain initialization safety.