AlgoMaster Logo

Required Members

Medium Priority18 min readUpdated June 6, 2026

The required modifier (C# 11) tells the compiler that a property or field has to be set when the object is created, with the rule enforced at the call site rather than buried inside a constructor. It's the missing piece between init-only properties (which made object initializers safe for immutable types) and full safety (where forgetting to set a value becomes a compile error, not a NullReferenceException at runtime). This lesson covers how required works, how it interacts with init, set, constructors, records, inheritance, and JSON deserialization, and when to use it instead of a long constructor parameter list.

The Problem required Solves

Init-only properties give us a clean way to build immutable data classes with object initializer syntax. The shape looks like this:

The code compiles, runs, and prints a product with an empty name and a price of zero. Nothing in the type system stopped the caller from forgetting both Name and Price, the two values an e-commerce product actually needs. The class author has no way to say "you must provide these" short of writing a constructor that takes them as parameters, which forfeits the object initializer syntax that init-only properties were designed for.

The required modifier closes this gap. It flips the default: the property has to be assigned in the object initializer (or in a constructor marked with [SetsRequiredMembers]), and the compiler refuses to build code that forgets.

Drop either property from the initializer and the build fails. The class now expresses its real contract in the type system: a Product without a Name and a Price is not a Product you can construct.

The CS9035 Compile Error

When the caller forgets a required member, the compiler reports CS9035. Take the same Product class:

The build fails with:

The error fires per missing member. Forget two and you get two CS9035 errors, one for each. The fix is to assign the missing member in the initializer:

A required member can be assigned to its declared type's default value if that's genuinely what you want, but you have to write the assignment. The point of the diagnostic is to force a conscious choice at the call site, not to second-guess the value.

The compiler runs the check at the construction site, not at the property declaration. The class author marks members as required, and every caller that builds an instance has to satisfy them.

required With init, set, and Fields

The required modifier doesn't only work with init. It pairs with any settable member: set accessors, init accessors, and even public fields. All three combinations behave the same way at the construction site (the compiler demands an assignment), but they differ in what happens afterwards.

The required keyword and the underlying mutability are independent. required controls whether the call site has to assign the member when the object is built. init versus set versus a public field controls whether the member can change afterwards. Mixing them gives you four useful shapes:

Member shapeMust assign at construction?Can change afterwards?
{ get; init; }NoNo
{ get; set; }NoYes
required { get; init; }YesNo
required { get; set; }YesYes

For a typical e-commerce data class, required { get; init; } is the most common pick: callers must supply the value, and the object is immutable afterwards. The other combinations show up when a class genuinely needs the value to change over time (for example, OrderStatus starts at Placed and moves through Shipped and Delivered).

A required field works the same way but without accessors:

Public fields are still rare in well-designed C# code (properties are preferred), but required works on them for the unusual cases where a field is appropriate.

[SetsRequiredMembers] On Constructors

A constructor that assigns the required members itself shouldn't force every caller to repeat those assignments in an object initializer. The [SetsRequiredMembers] attribute on a constructor tells the compiler "this constructor takes responsibility for the required members; callers don't have to."

The attribute lives in System.Diagnostics.CodeAnalysis, so it needs that using directive. Once applied, the compiler treats every required member as satisfied for that constructor. The attribute is a promise: if you forget to assign one of the required members inside the constructor body, the compiler does not catch the mistake. You said you'd handle it; you have to actually handle it.

A common pattern is to keep a parameterless constructor (or use the implicit one) for callers who prefer object initializers, and add one or two [SetsRequiredMembers] constructors for the cases where positional construction is more convenient. Both call styles work, and the class still expresses that Name and Price are mandatory.

[SetsRequiredMembers] doesn't generate any extra runtime work. It's a compile-time hint to the diagnostic, nothing more. The generated IL is the same as any other constructor.

required With Records

Records support required exactly like classes do, but the interaction with positional records is worth a closer look. A positional record (the record Product(string Name, decimal Price) form) generates init-only properties for each parameter, and the primary constructor is implicitly marked [SetsRequiredMembers] since it assigns every member. Nothing extra is needed.

The values come through the constructor, so the question of "did the caller forget" doesn't arise. Positional records don't need required for those parameters.

The case where required is useful is a property-based record: a record with a body and explicit init-only properties.

This shape gives you the benefits of records (value-based equality, with expressions, ToString deconstruction) plus required-member enforcement. with expressions work as expected: the result already has the required values from the source, so the with initializer only needs to override what's changing.

A record struct follows the same rules. The choice between class records and struct records is a separate topic; for required purposes, both behave identically.

required With Primary Constructors

C# 12 added primary constructors on regular (non-record) classes and structs. The interaction with required is subtle because a primary constructor on a class does not implicitly act like a record's primary constructor: it doesn't generate properties, doesn't satisfy required members, and isn't marked [SetsRequiredMembers] by default.

The primary constructor parameters seed the initializers, but the compiler still requires the object initializer because the primary constructor isn't marked [SetsRequiredMembers]. There is no syntax to apply the attribute to a primary constructor directly. The practical workaround is to add a regular constructor with the attribute and keep using positional construction:

For classes that lean on required, regular constructors with [SetsRequiredMembers] are usually a better fit than primary constructors. For records, where the primary constructor is implicitly attributed, primary syntax is the natural choice.

required and Inheritance

Required members propagate down inheritance hierarchies. A derived class inherits every required member from its base, and the construction site has to satisfy all of them, base and derived alike.

Skip any of the three and the compiler reports CS9035 for the one you missed. The required-ness travels with the property; you can't drop the modifier on the derived side, and you can't accidentally relax the rule by overriding without it.

The interaction with [SetsRequiredMembers] on the base class is the place where surprises tend to happen. A derived constructor that calls a base constructor marked [SetsRequiredMembers] does not inherit the attribute. The derived constructor still has to either be marked itself or leave the initializer to handle the base's required members. The compiler treats each constructor as its own contract.

Both constructors carry the attribute. The base constructor handles Name; the derived one handles DownloadUrl and chains to the base. Without the attribute on the derived constructor, callers would still have to write an object initializer.

A derived class can also re-declare a base required member with new, but the rule is the same: the compiler tracks required-ness on the actual declared property, and the call site still has to assign it. Hiding a required member with a non-required one does not silently relax the contract.

The diagram shows the propagation. Every required member from Product is still required for DigitalProduct, plus DigitalProduct adds its own.

JSON Deserialization

System.Text.Json understands required starting in .NET 7. When deserializing into a type with required members, the deserializer treats the JSON properties like an object initializer: every required member must appear in the JSON (or the deserialization throws), and missing required members produce a JsonException at runtime instead of a silent default.

The successful case fills Name and Price from the JSON and leaves Category as its default. The failing case omits Price, and System.Text.Json throws with a clear message listing the missing properties.

A constructor marked [SetsRequiredMembers] interacts with deserialization too. If System.Text.Json calls a constructor that's marked, it treats the required members as satisfied by the constructor and only validates members the constructor didn't take as parameters.

The behavior in older versions of the library or in third-party serializers (Newtonsoft.Json, for example) is different. Before adopting required for types you deserialize, check that your serializer understands the modifier. System.Text.Json in .NET 7+ does; Newtonsoft.Json (as of late 2024) does not enforce required and may silently leave the property at its default. When in doubt, write a small test that deserializes a JSON document missing a required member and verify the exception fires.

Required-member enforcement during deserialization is essentially free. The serializer already tracks which properties it has seen, so checking a "must be present" list at the end is a small extra pass.

Migrating Long Constructor Parameter Lists

The classic way to make construction safe in C# is a constructor that takes every required value as a parameter. For two or three values, that works. For ten, it produces this:

Every caller has to remember the order of ten positional arguments. Optional values get awkward (overloads pile up, default values cover only the trailing parameters). IDE tooling helps, but the call site is still noisy and easy to get wrong.

required plus init-only properties replaces this pattern with a single class definition and named-property construction:

The call site is self-documenting (each value is named), the order of fields doesn't matter, and the compiler still catches missing required values. Optional fields (BillingAddress, Tax, Shipping, Total, PlacedAt) sit alongside required ones, each with their own default. The class doesn't need overloads, optional parameters, or builder objects.

A side-by-side comparison:

ApproachRequired values enforced at compile time?Optional values?Reordering breaks callers?Named at call site?
Long positional constructorYesTrailing only, via defaultsYesNo
Builder patternRuntime check, not compile timeFree orderNoYes
required + initYesAny order, with defaultsNoYes

The required approach gets compile-time safety, free ordering, and call-site clarity in one shape. It's the modern default for data classes with more than two or three fields.

When required Does Not Fit

required is a great fit for data classes where the type system should enforce "you must provide this." It's a poor fit when:

  • The class needs custom logic before assignment, such as validation that spans multiple fields. The validation has to live somewhere, and an object initializer doesn't give you a natural place for cross-field rules. A factory method or a constructor that runs validation is cleaner.
  • The class is part of a public API that needs to stay binary-compatible with older callers. Adding required to an existing property is a breaking change for every caller that didn't set it.
  • The type is going through a serializer that doesn't understand required (some older libraries, some non-JSON formats). The compile-time check passes, but the runtime behavior is silent default values, which can be worse than a clear constructor that throws.

For the cases where required does fit, it removes a class of bug that init-only properties left open: callers who simply forgot. The compiler catches it, the call site reads as a checklist of what's needed, and the class author doesn't have to write a custom validator just to enforce "this field is mandatory."

Quiz

Required Members Quiz

10 quizzes