C# 11 proposes the new keyword required
that can apply to an instance property or an instance field declaration within a class
, a record
or a struct
.
1 2 3 4 |
class Foo { internal required int _RequiredField; internal required int RequiredProp { get; set; } } |
This keyword forces the caller to initialize the required field and property in the object initializer scope.
required init Property
This is quite a welcomed addition to improve object initialization scenario, especially concerning properties with an init setter that can only be initialized in the constructor and in the object initializer scope.
Limitations of Required Members
The keyword required
comes with a few limitations illustrated by the screenshot below:
required
cannot apply to a private member since the keyword’s restriction acts outside of the parent class, when a caller instantiates it. Notice that a field like_RequiredField
in the first code sample should not be mutable publicly, as explained into this NDepend rule Fields should be declared as private. Thusrequired
field is a code smell.required
cannot apply to a static member since it is designed to be used only at object instantiation time.required
cannot apply to a read-only member that can only be assigned from within a constructor.
Why required members?
Object initializer scope was introduced to overcome some constructor well-known limitations:
- Positional: With constructor, position of parameters must be respected by the caller.
- Lack of flexibility: if the client only wants a few states to be initialized (quite a common need in tests) he/she has to provide default values for other constructor parameters…
- Overloading: …unless the constructor provides many overloads or default parameters which leads to confusion.
- Breaking change: When introducing a new property, either all constructors must provide a default parameter or clients get broken.
- Redundancy: in a hierarchy of classes, when one add a new property to the base class, its constructor(s) has a new parameter that must be added in the constructors of all derived classes.
Until now it was not possible to require object initializer scopes to set a member which led to favor constructor in such scenario.
Required Member and Constructor
The keyword required
is actually part of the public contract of all constructor(s) of the class. The compiler doesn’t attempt to detect if a constructor actually assigns it:
However the new .NET 7.0 attribute System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute
can tag a constructor to relieve this contract on the tagged constructor only. Thus this code below compiles properly:
1 2 3 4 5 6 7 8 9 10 11 12 |
using System.Diagnostics.CodeAnalysis; var foo = new Foo(); class Foo { [SetsRequiredMembers] internal Foo() { this.Prop = "hello"; } internal required string Prop { get; init; } } |
If in the code above if we comment this.Prop = "hello";
a CS8618 warning on the Foo()
constructor explains that the non-nullable property Prop
must contain a non-null value when exiting the constructor.
required and Property Overriding
There are a few limitations when it comes to required
and property overriding:
- The keyword cannot be used in the context of an interface. As we saw the keyword
required
influences the public contract of constructor(s) and an interface doesn’t have any constructor. required
cannot be used in the context of explicit implementation overriding.- When overriding a
required
virtual or abstract property, the keywordrequired
must be precised again.
required and record C# 9.0 positional property syntax
Since there is no parameterless constructor generated by the C# 9.0 positional property syntax, there is no need for the required
keyword in this situation.
required decompiled
The keyword required
leads the C# compiler to tag the parent class and required properties and fields with the new .NET 7.0 attribute System.Runtime.CompilerServices.RequiredMemberAttribute
.
Conclusion
With constructors and object initializer scopes, C# has two syntaxes to deal with object instantiation. We saw that the new required
keyword is a welcomed addition for object initializer situations. However this new keyword is not well suited when there is a mix of constructors overloading and init
properties because of the need to tag constructors with [SetsRequiredMembers]
which is not smooth syntax.
I find a lot wrong with your argument on the problems that required fixes.
“Positional: With constructor, position of parameters must be respected by the caller.”
This is not true, you can always choose to name your arguments so your constructor will look almost identical to your property initialization.
“Lack of flexibility: if the client only wants a few states to be initialized (quite a common need in tests) he/she has to provide default values for other constructor parameters…”
You shouldn’t be listing out every possible argument in the constructor. Simply reserve that for ones that are required and let users set the properties, by other means, on those they wish to set.
“Overloading: …unless the constructor provides many overloads or default parameters which leads to confusion.
“Breaking change: When introducing a new property, either all constructors must provide a default parameter or clients get broken.”
Once again, only add arguments to the constructor that you need to take. Also, you’d break all clients by adding a required property just as you would by adding a new argument to the constructor.
At the end of the day, required is a way to try and replace constructors…something that I don’t understand. There is nothing wrong with constructors and this syntax really just goes to prove it. It’s yet another junk feature added in that only furthers bloating the language.