C# 9.0 Features

C# 9.0 was released with .NET 5.0. These two were released simultaneously in .NET Conf 2020.
 
Today we are going to discuss three features from C# 9.0. In order to use C# 9.0 and .NET 5.0, we should have Visual Studio 16.8 or above. As you might have already know .NET is the next version from .NET Core 3.1. The reason .NET 5.0 was chosen so that there is no confusion with the .NET version 4.* and Microsoft wanted to get rid of the Core because going forward this is the .NET version. Though for ASP.NET it is going to be still named as ASP.NET Core 5 because there is already AP.NET MVC 5.0. Here to avoid confusion, Core is retained. Same is applicable for Entity Framework Core. Entity Framework 5.0 will be named as Entity Framework Core 5 because there is already EF 5.0 and EF 6.0. Let us dive into features.
 

Init Only setters

 
Using the getter and setter we can create mutable objects, meaning after creating objects, we can keep changing the value of the properties associated with objects.
 
Assume that we have a class as below,
  1. public class WeatherForecast {  
  2.     public DateTime Date {  
  3.         get;  
  4.         set;  
  5.     }  
  6.     public int TemperatureC {  
  7.         get;  
  8.         set;  
  9.     }  
  10.     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);  
  11.     public string Summary {  
  12.         get;  
  13.         set;  
  14.     }  
  15. }   
The below code snippet will explain the mutable object.
  1. public IEnumerable < WeatherForecast > Get() {  
  2.     var rng = new Random();  
  3.     return Enumerable.Range(1, 5).Select(index => {  
  4.         var weather = new WeatherForecast {  
  5.             Date = DateTime.Now.AddDays(index),  
  6.                 TemperatureC = rng.Next(-20, 55),  
  7.                 Summary = Summaries[rng.Next(Summaries.Length)]  
  8.         };  
  9.         //the object weather is mutable.  
  10.         //here we can assign a new value to the property Summary.  
  11.         weather.Summary = "Changing the initial value.";  
  12.         return weather;  
  13.     }).ToArray();  
  14. }   
If we want to make this object immutable, let us see how “init” keyword can be leveraged in C# 9.0. Instead of “set”, we are going to use “init” in WeatherForecast model.
  1. public class WeatherForecast {  
  2.     public DateTime Date {  
  3.         get;  
  4.         init;  
  5.     }  
  6.     public int TemperatureC {  
  7.         get;  
  8.         init;  
  9.     }  
  10.     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);  
  11.     public string Summary {  
  12.         get;  
  13.         init;  
  14.     }  
  15. }  
What the “init” does is, it allows you to set the properties during the first time the object is created. So when you are creating the weatherforecast object, if you have inline declaration of the properties, it is going to allow you to assign. But after that, it will not allow you to assign any new values.
  1. public IEnumerable < WeatherForecast > Get() {  
  2.     var rng = new Random();  
  3.     return Enumerable.Range(1, 5).Select(index => {  
  4.         var weather = new WeatherForecast {  
  5.             Date = DateTime.Now.AddDays(index),  
  6.                 TemperatureC = rng.Next(-20, 55),  
  7.                 Summary = Summaries[rng.Next(Summaries.Length)]  
  8.         };  
  9.         //the object weather is immutable.  
  10.         //here we can't assign a new value to the property Summary.  
  11.         //this is a compile time error  
  12.         weather.Summary = "Changing the initial value.";  
  13.         return weather;  
  14.     }).ToArray();  
  15. }  
The init setter allow you to assign a value during the object initialization. This is extremely convenient feature which can get rid of lot of boiler plate code – declaring constructor and setting everything up in constructor.
 
Now let us get into next feature.
 
Record Type
 
Let us use the same WeatherForecast mode, but instead of “class” keyword, we will be using “record”
  1. public record WeatherForecast(DateTime Date, int TemperatureC, string Summary) {  
  2.     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);  
  3. }  
Here is a biggest advantage – we got rid of all the boiler plate code.
 
Let us take a look at the object initialization.
  1. public IEnumerable < WeatherForecast > Get() {  
  2.     var rng = new Random();  
  3.     return Enumerable.Range(1, 5).Select(index => {  
  4.         var weather = new WeatherForecast(DateTime.Now.AddDays(index), rng.Next(-20, 55), Summaries[rng.Next(Summaries.Length)]);  
  5.         //the object weather is immutable.  
  6.         //here we can't assign a new value to the propery Summary.  
  7.         //this is a compile time error.  
  8.         //by default record setter is init only  
  9.         weather.Summary = "Changing the initial value.";  
  10.         return weather;  
  11.     }).ToArray();  
  12. }  
Another important feature comes with record type which is “with” expression.
 
Usage
  1. public IEnumerable < WeatherForecast > Get() {  
  2.     var rng = new Random();  
  3.     return Enumerable.Range(1, 5).Select(index => {  
  4.         var weather = new WeatherForecast(DateTime.Now.AddDays(index), rng.Next(-20, 55), Summaries[rng.Next(Summaries.Length)]);  
  5.         var newWeather = weather with {  
  6.             TemperatureC = 25  
  7.         };  
  8.         //Here Summary will be same as from the previous object,  
  9.         //only TemperatureC will be changed  
  10.         Console.WriteLine($ "New Summary Value = {newWeather.Summary}");  
  11.         return weather;  
  12.     }).ToArray();  
  13. }  
Here using “with” keyword, a new object will be created from the base and can be modify some of the properties.
 
The last but not the least feature
 

Top level statements

 
Top level code or statements are nothing but the code available in Program.cs as below 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. namespace dotNET5 {  
  6.     public class Program {  
  7.         public static void Main(string[] args) {  
  8.             CreateHostBuilder(args).Build().Run();  
  9.         }  
  10.         public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => {  
  11.             webBuilder.UseStartup < Startup > ();  
  12.         });  
  13.     }  
  14. }  
We can get rid of the entire code and keep a simple line as below- this will get compiled and can see the “Hello World” on the output window. Try this code at your end and see.
  1. System.Console.WriteLine("Hello World");  
Honestly speaking, this is not useful as this is not what we are going to do in real life programming.
 
Let us modify the Program.cs class file as below and execute.
  1. using dotNET5;  
  2. using Microsoft.AspNetCore.Hosting;  
  3. using Microsoft.Extensions.Hosting;  
  4. CreateHostBuilder(args).Build().Run();  
  5. static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => {  
  6.     webBuilder.UseStartup < Startup > ();  
  7. });  
It behave exactly same as before. When this is helpful? In my opinion, this will be helpful when you are writing server less functions- for example, if you are using Azure functions or AWS Lambda, this kind of code is going to be very helpful.
 
That’s all I wanted to cover in this session. Thanks for reading my article. I appreciate your feedback in the comment section below.
Next Recommended Reading Feature Flag In .NET 6.0