In this article we’ll explore the new C# 11 required keyword that was introduced this week and see how it helps provide alternative ways of safely configuring objects.
This feature, like init-only properties and a number of previous language features, is aimed at improving the way we initialize objects that does not rely on constructor parameters.
C# 11 Prerequisites
Before we go too much farther, let’s discuss how to get set up with C# 11 so you can play with the required keyword in C# 11 along with the other new features.
C# 11 is an incremental upgrade to the C# programming language that was released earlier this week. Like most language upgrades C# 11 adds a number of new features to the C# syntax and is backwards compatible with prior versions of C#.
In order to use C# 11 and follow along with this code you will need:
- Visual Studio 2022 17.4.0 or greater installed
- A project targeting .NET 7 or greater
- The project’s LangVersion set to 11 or latest
The easiest way of upgrading is to open the Visual Studio Installer and click upgrade to ensure your Visual Studio environment is up to date. If you do not yet have Visual Studio 2022 or greater installed, you can always find the community edition available online.
Exploring a Typical C# 10 Class
Take a look at the following C# class representing an article:
public class Article
{
public Article(string title, string subtitle, string author, DateTime published)
{
Title = title;
Subtitle = subtitle;
Author = author;
Published = published;
}
public Article(string title, string author, DateTime published)
{
Title = title;
Author = author;
Published = published;
}
public string Title { get; set; }
public string? Subtitle { get; set; }
public string Author { get; set; }
public DateTime Published { get; set; }
public override string ToString()
{
if (string.IsNullOrWhiteSpace(Subtitle))
{
return $"{Title} by {Author} ({Published.ToShortDateString()})";
}
return $"{Title}: {Subtitle} by {Author} ({Published.ToShortDateString()})";
}
}
Note that this class has a number of properties, a pair of constructors, and a ToString
override.
This class declares the Title, Author, and Published properties as non-nullable (no ? after their data type). This means that these values should never be null.
The class also has a Subtitle property declared as a nullable string (string?), which indicates that null values are acceptable for this property.
To support this, the class offers two different constructors: one that takes in a subtitle and one that does not.
As it stands, this class serves its purpose and ensures that all required properties are set via constructor arguments. However, there are a few things about this class that might irritate developers.
The Problem with Constructors
First of all, you might notice that this class devotes a significant portion of its total lines of code to constructors. These constructors don’t actually do much other than map their arguments to different properties.
Secondly, as the number of properties in this class grows, these constructors will likely grow as well. It is likely that new arguments will be added to the constructor signatures. These additional arguments will lead to new lines of code being written to map their values into properties. Additionally, the addition of new properties may lead to the desire to add new overloads of the constructor, further expanding the impact of constructors on the class.
Third of all, these constructors don’t add much to the class beyond “boilerplate” logic of wiring arguments into the properties they go with. They are largely noise and opportunities for small mistakes to occur, introducing bugs.
Fourth of all, calling these constructors can look a little nasty at times.
Take the following sample invocation for example:
Article thisArticle = new("Introducing the C# 11 Required Keyword", "A new language feature that helps reduce boilerplate constructor code", "Matt Eland", new DateTime(2022, 11, 12));
Not only is this a very long line of code, but it takes a moment of time to mentally map what property each parameter corresponds to.
Furthermore, as we add new required constructor parameters, this line will only grow longer.
Many folks are fine with these things, and for them, that’s fine. However the C# language has been trying to reduce “constructor bloat” in recent years for those who want these capabilities.
Improving Construction with Object Initializers
One way that we’ve tried to reduce the need for long constructors has been to rely more on inline object initializers.
If we were to use an inline initializer, we could rewrite the class to only have a default constructor as follows:
public class Article
{
public string Title { get; set; }
public string? Subtitle { get; set; }
public string Author { get; set; }
public DateTime Published { get; set; }
public override string ToString()
{
if (string.IsNullOrWhiteSpace(Subtitle))
{
return $"{Title} by {Author} ({Published.ToShortDateString()})";
}
return $"{Title}: {Subtitle} by {Author} ({Published.ToShortDateString()})";
}
}
This would then let us create it as follows:
Article thisArticle = new()
{
Title = "Introducing the C# 11 Required Keyword",
Author = "Matt Eland",
Published = new DateTime(2022, 11, 12)
};
Console.WriteLine(thisArticle);
Note: the
new()
syntax here is using the target-typed new feature introduced in C# 9 and is not strictly necessary for this example. The code could also have saidnew Article()
This code works great, and this is a minimal way of constructing an instance of Article that has all required members configured.
However, there’s nothing explicitly forcing the caller to set all required properties, which results in the CS8618 compiler warning in the Article class definition as shown below:
This is effectively stating that it is technically possible to create an article without an Author or Title property. Even though these properties are defined as non-nullable in the class, their default values will be null so if the object initializer doesn’t set them, their values will remain null
and this may lead to errors.
To illustrate this, the following code is still valid for constructing an instance of an Article:
Article thisArticle = new()
{
// Note: no Title or Author properties
Published = new DateTime(2022, 11, 12)
};
Console.WriteLine(thisArticle);
This is where the new C# 11 required keyword can help us.
The C# 11 Required Keyword
With C# 11 we can now add the required keyword to properties to indicate that they must be set during initialization.
The code for this is as follows:
public class Article
{
public required string Title { get; set; }
public string? Subtitle { get; set; }
public required string Author { get; set; }
public required DateTime Published { get; set; }
public override string ToString()
{
if (string.IsNullOrWhiteSpace(Subtitle))
{
return $"{Title} by {Author} ({Published.ToShortDateString()})";
}
return $"{Title}: {Subtitle} by {Author} ({Published.ToShortDateString()})";
}
}
Now if you try to create a new instance of an Article but don’t set the required members, you get a CS9035 compiler error as shown below:
This shows us how the C# 11 required keyword can enforce non-null constraints on object initializations for classes that would prefer to use initializers instead of complex constructors.
Unlike past solutions, the required keyword makes the compiler responsible for enforcing setting of non-nullable properties. This ultimately leads to better quality since it now becomes impossible not to set required properties when initializing a new instance.
If you’re interested in more details on the C# 11 required keyword, I’d encourage you to check out the feature specification.
Before we wrap up, I want to stress a point here: we are not getting rid of constructors. Every class still has at least one constructor. Constructors remain an integral part of dotnet languages. We’re now simply aiming at using the default constructor more and more and doing our initialization outside of it with inline object initializers.
Final Thoughts on the C# 11 Required Keyword
Overall, I think I like the new required keyword, but I do have a few reservations.
On the plus side, I like the idea of relying less on custom constructors and having more of your code oriented towards solving business problems than initializing state.
I also like the fact that object initializers tend to be easier to read than constructors.
However, this does come with a price.
For one, every new keyword and alternative way of doing something we add to the C# language makes the language harder to learn. New learners, like my bootcamp students, now have a new keyword that they may encounter in their travels.
When new learners do encounter the C# 11 required keyword they will be forced to learn what it means, when to use it, and why they might choose to use it or not use it.
Additionally, this growing trend against complex constructors risks dividing C# users into those who prefer using object initializers vs those who prefer complex constructors. This will push organizations to have more principled stances in their style guides and force library authors to support both workflows.
Overall, I think the required keyword is interesting and I’m going to try a project or two without relying much on custom constructors to see how it feels.
I’d love to hear your opinion about the required keyword and constructors vs initializers in general.
Top comments (4)
Great stuff, thanks. I've always wondered: Can one use say, C# v11 with .Net 6? Is the question understood? Can I use newer language features on runtimes that weren't born with them by simply upgrading the compiler?
I use Visual Studio Code for its flexibility and more lightweight operation, so basically I just download the SDK's. Installing .Net 7 would mean I get a new compiler. Can I go .Net6 with C#11? Or .Net5 with C#10? Do you know?
Jose,
No, that won't work.
The
<TargetFramework>
you specify in your.csproj
file will set the .NET version of your project. This is independent of the .NET SDK version you have installed.So, for example, you could have
<TargetFramework>net5.0</TargetFramework>
in your project but have the latest .NET 7 SDK installed. Your project will use .NET 5.0 and the C# language version appropriate for .NET 5 (i.e. C# 9) and won't use or require any .NET 7 features.All .NET SDKs are backwards compatible with older versions of .NET and C# released with or before the .NET SDK version.
You can manually specify the C# version you want to use in your project using
<LangVersion>
, but you can only use versions earlier than the one associated with your project's<TargetFramework>
.For example, you can't have both
<TargetFramework>net5.0</TargetFramework>
and<LangVersion>10.0</LangVersion>
, but you could have<LangVersion>8.0</LangVersion>
.Also, if you have multiple versions of the .NET SDK installed, you can specify exactly which version (or minimum version) should be used with your project using the
global.json
file.Alright, I guess that settles it.
Thank you! I know that C# 11 requires .NET 7 or above. It is usually possible to have multiple.NET SDKs installed, however, so you can continue to develop with older .NET versions.