Monday, September 12, 2022

C# 11.0: Required Members

Just a couple of months to go for .NET 7 final release, as you might already know, some of the C# 11.0 features are already available. 

In this post, let's go through a new feature that is ready to use from Visual Studio 2022 version 17.3, and that is the feature to specify required members in a class, struct or record (including record struct). And that's by using the brand new modifier: required.

Let's go by an example. I will be using a class for simplicity. Consider the following Person class.
public class Person
{
    public string FirstName { getset; }
 
    public string LastName { getset; }
 
    public DateOnly? DateOfBirth { getset; }
}
Generally, it considers a bad practice if you are letting someone to create an object without satisfying the minimum requirements of that particular object. For example here in our scenario, we shouldn't let someone create a Person without specifying FirstName and LastName. Those are kind of mandatory for every person.

So here usually what we do is introduce a constructor and specify the parameters that are needed to create a valid object.
public class Person
{
    public string FirstName { getset; }
 
    public string LastName { getset; }
 
    public DateOnly? DateOfBirth { getset; }
 
    public Person(string firstNamestring lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}
Now, this is looking good. But what if we remove the constructor. The caller has no idea what basic properties are needed to be set when creating a Person object. So we need to have some other way to declare which properties are required.

So C# 11.0 introduced this brand new modifier: required which can be used as follows.
public class Person
{
    public required string FirstName { getset; }
 
    public required string LastName { getset; }
 
    public DateOnly? DateOfBirth { getset; }
}
Now the caller can create a Person object like below using object initialization.
Person person = new()
{
    FirstName = "John",
    LastName = "Doe"
};
And this is nice, isn't it? Now we don't even need to declare a constructor to accept the required parameters. I personally prefer object initialization instead of using a constructor, because say you have a lot of required properties, then in your constructor, you are going to have a lengthy parameters list.

And if you attempted to create a Person without specifying the required parameters, the compiler will emit an error.
Person person = new();
// Required member 'Person.FirstName' must be set in the object initializer or attribute constructor.
// Required member 'Person.LastName' must be set in the object initializer or attribute constructor.
Now say, you have some already written code where you are using a constructor to set required properties and you have updated your required properties with required modifier.
public class Person
{
    public required string FirstName { getset; }
 
    public required string LastName { getset; }
 
    public DateOnly? DateOfBirth { getset; }
 
    public Person(string firstNamestring lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}
And the existing callers would be creating an object like below.
Person person = new("John""Doe");
// Above will throw a compile error
Now here you are going to get a compile error because the compiler doesn't know that from your constructor you are setting values to required properties.  In this case, you need to attribute the constructor with [SetsRequiredMembersattribute like below.
[SetsRequiredMembers]
public Person(string firstNamestring lastName)
{
    FirstName = firstName;
    LastName = lastName;
}
Note: This [SetsRequiredMembersattribute needs to be used with care.

Let's say for some reason, later you have decided DateOfBirth is going to be a required property. Basically something like below.
Person person = new("John""Doe");
// Person is getting created using the constructor, but required DateOfBirth isn't being set
// No compile errors here because [SetsRequiredMembers] is masking the error
 
public class Person
{
    public required string FirstName { getset; }
 
    public required string LastName { getset; }
 
    public required DateOnly DateOfBirth { getset; }
 
    [SetsRequiredMembers]
    public Person(string firstNamestring lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}
This code will get compiled just fine, but logically it isn't correct. [SetsRequiredMembers] attribute is masking the expected error which is DateOfBirth isn't set. So that's something to keep in mind.

Most of the time, required properties shouldn't be allowed to be mutated later, so we can write a more complete code something like below. Here for the FirstName and LastName properties, I have used init keyword (introduced with C# 9.0) to specify that the required parameters should get set only upon the object construction.
Person person = new()
{
    FirstName = "John",
    LastName = "Doe"
};
 
public class Person
{
    public required string FirstName { getinit; }
 
    public required string LastName { getinit; }
 
    public DateOnly? DateOfBirth { getset; }
}
Hope this helps.

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment