blog post

C# Init Only and Required Properties

Init only properties

C# 9.0 introduced a new type of a property accessor called init.
The init only property can be assigned only during object creation and can't be changed further. This enforces immutability.

Let's see how to define init only properties:

csharp
public record Product { public int Id { get; init; } public string Name { get; init; } public decimal Price { get; init; } } // Create an instance of Product's record var product = new Product { Id = 1, Name = "Phone", Price = 500.00m };

The best practise when creating a record is declaring all of its properties as init only. This ensures that properties can't be changed, ensuring the immutability of the record's data. Although you can define init properties in a class too.

init only properties have the same behaviour as set properties. You can use a backing field for the property if needed:

csharp
public record Product { private readonly decimal _price; public required decimal Price { get => _price; init => _price = value; } // Other properties... }

Required properties

A new keyword called required was introduced in C# 11, which is a great addition to init only properties:

csharp
public record Product { public required int Id { get; init; } public required string Name { get; init; } public required decimal Price { get; init; } }

This ensures that all properties marked with required keyword, should be assigned when creating an object, otherwise a compilation error is raised:

csharp
// This code doesn't compile as Price property is not assigned var product = new Product { Id = 1, Name = "Phone" };

init and required keywords make a great pair together. required ensures that init only properties are assigned during objection creation, as these properties can't be changed further.

Positional Records

C# also provides a shortened form of record declaration:

csharp
public record Product(int Id, string Name, decimal Price); var product = new Product(1, "PC", 1000.00m);

A single line of code! We assign all the properties using a record's constructor which is called a primary constructor. Under the hood this code is translated to a classic form of a record declaration with all properties being init only. This form of record is also called a positional record as all properties under the hood are created in the exact order as their position in the primary constructor. Positional form of declaration looks really elegant and concise when a record doesn't have a lot of fields.

When creating a record's object using a primary constructor - you are forced to assign all the properties. Though the required keyword is not used here.

Init Only and Required Properties Important Notes

1. Required keyword only works at compile time

When an object is created at the runtime, required keyword has no effect, it doesn't throw exceptions even if the field is not assigned with a value. required works only at compile time. Although there is one exception: objects serialized and deserialized using System.Text.Json in NET 8 can throw exceptions when required properties are not assigned with a value.

2. Nullable properties can be marked as required

It is perfectly fine to mark nullable properties as required. It forces you to assign it a real value or null when creating an object. Let's add a nullable Description property to the Product record:

csharp
public record Product { public required int Id { get; init; } public required string Name { get; init; } public required string? Description { get; init; } public required decimal Price { get; init; } } var product = new Product { Id = 1, Name = "PC", Price = 1000.00m, Description = "Some amazing PC" // Assign some value }; var product2 = new Product { Id = 1, Name = "PC", Price = 1000.00m, Description = null // Explicitly assign null here };

You should really consider using required keyword for all types of properties whenever appropriate.

3. Using constructor with required properties

Let's see the following code where we create a class with required properties and a constructor:

csharp
public class User { public User(string name, int age) { Name = name; Age = age; } public required string Name { get; init; } public required int Age { get; init; } } var user = new User("Anton", 30); // This doesn't compile

This code doesn't compile as compiler tells that required properties are not assigned, even if they are assigned from the constructor. To fix the code you need to add property assignments along with the constructor:

csharp
var user = new User("Anton", 30) { Name = "Anton", Age = 30 }; // Now this compiles

This one is a real caveat and you should consider this strange behaviour when modelling classes.

Hope you find this blog post useful. Happy coding!

Improve Your .NET and Architecture Skills

Join my community of 1000+ developers and architects.

Each week you will get 2 practical tips with best practises and architecture advice.