C# A to Z: Assignment with Init-Only Setters
Shahed Chowdhuri @ Microsoft
Posted on January 4, 2021
Update: Due to new personal commitments and more work commitments in 2021, I wasn't able to make much progress with my weekly C# A-Z series.
For now, I'll focus on some new content for my regular blog (WakeUpAndCode.com) and hope to revisit the A-Z series with .NET 6.
Original Post:
Introduction
This is the first of a new series of posts on C# topics. In this series, we’ll cover 26 topics in 2021, titled C# A-Z! This series will cover mostly C# 9 topics with .NET 5, plus some additional language features that have been around with earlier releases of C#.
If you're not familiar with top-level programs in C# 9, make sure you check out the Prelude post for this series:
Let's kick things off with the letter A!
- Assignment with Init-Only Setters
Source code:
Prerequisites
Before you get started, please install the latest version of .NET and your preferred IDE or code editor, e.g. Visual Studio 2019 or Visual Studio Code.
- Install .NET: https://dot.net
- Install Visual Studio 2019: https://visualstudio.microsoft.com/vs/
- Install Visual Studio Code: https://code.visualstudio.com/
Without Init-Only Setters
Init-only setters were introduced with C# 9, allowing developers to create immutable properties in C# classes. Before we get into the new feature, let's explore how your code would look without this feature.
The PatientInfo class in the >NET Core 3.1 project contains a set of properties for a hospital patient, as shown below:
namespace assign_netcore31.Models
{
public class PatientInfo
{
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public bool IsWell { get; set; }
}
}
Each of these properties have a getter and setter, and can be initialized in a Program that uses this class.
using assign_netcore31.Models;
using System;
namespace assign_netcore31
{
class Program
{
static void Main(string[] args)
{
// ...
// create a new patientInfo object
var patientInfo = new PatientInfo
{
Id = 12345,
LastName = "Doe",
FirstName = "John",
IsWell = true
};
Console.WriteLine($"ID={patientInfo.Id}");
Console.WriteLine($"Full Name={patientInfo.FirstName} {patientInfo.LastName}");
Console.WriteLine($"Sick? {!patientInfo.IsWell}");
// ...
}
}
}
If you run the program from the sample project, you should see the following output. Note that you can easily re-assign fields that have already been initialized.
Hello World from .NET Core 3.1!
ID=12345
Full Name=John Doe
Sick? False
New Id=99999
New last name=Smith3000
Let's say we want to make the Id field "immutable", i.e. prevent it from being changed once it has been initialized. We could remove the setter from its property to prevent intialization. We would also have to create a constructor to pass in an Id value for initial assignment.
The following class PatientInfoWithId, illustrates how this can be achieved.
namespace assign_netcore31.Models
{
public class PatientInfoWithId
{
public int Id { get; }
public string LastName { get; set; }
public string FirstName { get; set; }
public bool IsWell { get; set; }
public PatientInfoWithId(int id)
{
Id = id;
}
public PatientInfoWithId(int id, string lastName, string firstName, bool isWell)
{
Id = id;
LastName = lastName;
FirstName = firstName;
IsWell = isWell;
}
}
}
The following Program makes use of the above class, to initialize a patient object with an Id value specified in the constructor.
using assign_netcore31.Models;
using System;
namespace assign_netcore31
{
class Program
{
static void Main(string[] args)
{
// ...
// create a new patientInfoWithId object
var patientInfoWithId = new PatientInfoWithId(67890);
patientInfoWithId.LastName = "Smith";
patientInfoWithId.FirstName = "Joe";
patientInfoWithId.IsWell = false;
Console.WriteLine($"ID={patientInfoWithId.Id}");
Console.WriteLine($"Full Name={patientInfoWithId.FirstName} {patientInfoWithId.LastName}");
Console.WriteLine($"Sick? {!patientInfoWithId.IsWell}");
}
}
}
If you re-run the program from the 3.1 sample project (assign-netcore31), you should also see the following output.
...
ID=67890
Full Name=Joe Smith
Sick? True
By uncommenting the commented line of code provided, you could attempt to reassign the Id value.
// UNCOMMENT BELOW to change Id: doesn't work
//patientInfoWithId.Id = 99999;
Console.WriteLine($"New Id={patientInfoWithId.Id}");
}
However, the Id value cannot be reassigned in this way, since there's no setter for the property. To achieve the same thing with fewer lines of code, let's jump into the .NET 5 project using C# 9.
Using Init-Only Setters
In the .NET 5 sample project, the PatientInfo class also contains a similar set of properties for a hospital patient, as shown below:
namespace assign_net5.Models
{
public class PatientInfo
{
public int Id { get; init; }
public string LastName { get; init; }
public string FirstName { get; set; }
public bool IsWell { get; set; }
}
}
The big difference is the init keyword used for the Id and LastName fields:
public int Id { get; init; }
public string LastName { get; init; }
This allows us to use this class in a simpler way in the .NET 5 program.
using assign_net5.Models;
using System;
Console.WriteLine("Hello World from .NET 5!");
var patientInfo = new PatientInfo
{
Id = 12345,
LastName = "Doe",
FirstName = "John",
IsWell = true
};
Console.WriteLine($"ID={patientInfo.Id}");
Console.WriteLine($"Full Name={patientInfo.FirstName} {patientInfo.LastName}");
Console.WriteLine($"Sick? {!patientInfo.IsWell}");
This should result in the following output:
Hello World from .NET 5!
ID=12345
Full Name=John Doe
Sick? False
New Id=12345
New last name=Doe
Note that we were easily able to assign the property values without the need for any explicit constructor. Better yet, the init keyword prevents us from trying to reassign the Id or LastName properties.
If you try commenting out the remaining code provided, the code won't compile (as intended).
// UNCOMMENT BELOW to change Id: doesn't work with C#9 in .NET 5
//patientInfo.Id = 99999;
Console.WriteLine($"New Id={patientInfo.Id}");
// UNCOMMENT BELOW to change last name: doesn't work with C#9 in .NET 5
// patientInfo.LastName = "Smith3000";
Console.WriteLine($"New last name={patientInfo.LastName}");
Conclusion
In this blog post, we have covered the super-simple init acccessor in C# 9 using .NET 5. We spent most of our time exploring a .NET Core 3.1 version of a similar project, which resulted in more code. This helped to illustrate the simplicity and efficiency of the init keyword, when used to create immutable C# properties.
What's Next
This blog post is the first in the C# A-Z series in 2021. Stay tuned for a total of 26 different topics in the weeks and months ahead!
Up next:
- A is for: Assignment with Init-Only Setters
- B is for: Bits in Native-Sized Integers
- C is for: TBA
- D is for: TBA
- E is for: TBA
- F is for: TBA
- G is for: TBA
- H is for: TBA
- I is for: TBA
- J is for: TBA
- K is for: TBA
- L is for: TBA
- M is for: TBA
- N is for: TBA
- O is for: TBA
- P is for: TBA
- Q is for: TBA
- R is for: TBA
- S is for: TBA
- T is for: TBA
- U is for: TBA
- V is for: TBA
- W is for: TBA
- X is for: TBA
- Y is for: TBA
- Z is for: TBA
References
- C#9 features: https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9
- Init-only design: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/init
- Deep dive with Dave Brock: https://daveabrock.com/2020/06/29/c-sharp-9-deep-dive-inits
All future code samples will be added to the existing GitHub repository:
Posted on January 4, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.