Init-only properties

Today in C# you have the possibility to use object initializers. Using object initializers you have the possibility to set properties of an object immediately after the object is created and do this in one statement. It looks like:

1
2
3
4
5
new Animal
{
    Name = "Dog",
    NumberOfLegs = 4
}
But to do this the properties (Name and NumberOfLegs in this case) need to have public setters. And when that is the case you can also change the value of a property later. If you don’t want the properties to be changed later you can do this by making the setters private and add the properties to the constructor. The Animal class would then look like the following:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Animal
{
    public Animal(string name, int numberOfLegs)
    {
       Name = name;
       NumberOfLegs = numberOfLegs;
    }

    public string Name { get; private set; }
    public int NumberOfLegs { get; private set; }
}
Because we added the private accessor keyword to the setter of the properties they can only be set by the object itself. The disadvantage is that you can not use object initializers anymore (which is sometimes also a good thing because filling your model through the constructor can help keeping your model valid). So, to solve this, in C# 9.0, you can use the ‘init’ accessor instead of the private accessor. This means the property can only be set in the object initializer. After that it is not possible anymore to change the values. The code would look as follows:
1
2
3
4
5
public class Animal
{
    public string Name { get; init set; }
    public int NumberOfLegs { get; init set; }
}
When creating an animal you can just use the object initializer as usual but you cannot change the values of the properties later:
1
2
3
4
5
6
var animal = new Animal
{
    Name = "Dog",
    NumberOfLegs = 4
};
animal.Name = "Cat"; // Will not compile
My opinion: When programming a good model for your application and keep the integrity in the model, it is always good to force properties to be set through the constructor. In that case ‘init-only’ properties are not required. On the other hand I have seen a lot of code where this pattern isn’t used or objects just contain a large amount of properties that aren’t set all the time. A constructor is not useful in that case. In those cases the ‘init-only’ properties are a very good way to not allow properties to be modified later. So I think it is a good addition to the language which will help people make better code.

Record classes

With ‘init-only’ properties we have made the properties on that object immutable. If you want to do that with the whole object C# 9.0 brings you a solution in the form of ‘record classes’. You can create a record class by adding the ‘data’ keyword before the class definition. When you do that an instance of that class will be immutable and more behave like a value type then as an object. I won’t go into detail explaining how this all works because it is perfectly described in this blogpost:
https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/#records

But in the end you can write a record class as follows:

1
2
3
4
5
public data class Animal
{
    public string Name { get; init; }
    public int NumberOfLegs { get; init; }
}
This can be written even shorter in the following way:
1
public data class Animal { string Name; int NumberOfLegs; }
In the above example you get an Animal with 2 properties that can only be set through the object initializer. Because record classes behave like values and are immutable a clone of the object is made when you assign it to another variable. This works just like structs and other value types. But sometimes you want the same object with just some properties given another value. For that case the ‘with’ expression is added. After the ‘with’ expression you can use an object initializer in the same way you do that when you instantiate a new object. It looks as follows:
1
var otherAnimal = animal with { Name = "Lion" };
Just like structs record classes also automatically override the Object.Equals method with a method that compares all the properties. In that way value-based equality is also supported.

My opinion: My first question when I read about record classes was: “Why not just use structs?”. And the answer to that took me a while to understand. Then I found (an already old article) on c-sharpcorner.com: https://www.c-sharpcorner.com/article/candidate-features-for-c-sharp-9/.
The answer to my question they give there is as follows:
“Structs are a thing you have to have some discipline to implement. You don’t have to make them immutable. You don’t have to implement their value equality logic. You don’t have to make them comparable. If you don’t, you lose almost all of their benefits, but the compiler doesn’t enforce any of these constraints.”
And I think they are right here. With record classes you have something natively supported in the language that under the hood generates all the code so that it really behaves like a value type and equality is correctly implemented. So my conclusion is that record classes are a good addition to C#.

Top-level programs

When you write a program in C# you always need a class with a static Main method. This is the starting point of every application. A simple “Hello World” program looks like this: using System;

1
2
3
4
5
6
7
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}
The idea behind top-level programs is to remove a lot of boilerplate code here that’s always the same. So instead of defining a class, adding a static Main method and then start with your application code they decided you can leave everything out and just start with your application code immediately after the using statements. It can look as follows then: using System;

1
Console.WriteLine("Hello World!");

The argument here is that you don’t need to write the same boilerplate code every time and that it should be more easy for newcomers to the language. Of course you can only have one file in your application that does this (just like you could have only one static Main method).

My opinion: This is one of the features where I don’t fully understand why they added this. I understand that it saves some code but this isn’t code that you have a lot of in your application. It is not repeated in every class so you don’t save a lot of code. The other argument is that it will be more easier for newcomers but I personally doubt if that is the case. For me, and also for a newcomer, I think it is very clear that every program has one entry point and that is where the static Main method is. Using top-level programs that stays the same but then you can only have one file where you start your application code right after the using statements. A bit less clear in my opinion.

Improved pattern matching

Since C# 7.0 there have been a lot of improvements and additions to pattern matching in C#. In C# 9.0 this is also the case. As a programmer you get more and more possibilities to use patterns in switch statements. The followings are added:

  • Simply type patterns –> Just switch on type without declaring an unused variable
  • Relational patterns –> Use a nested switch expression
  • Logical patterns –> Use keywords like and, or and not in your patterns

All the above patterns are described here:
https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/#improved-pattern-matching

My opinion: I think it is always good to have more possibilities to specify the cases in your switch statements. In this way code can become more clear and easy to read.

Improved target typing

With “target typing” an expression gets it type from the context it is in. This is already the case in many situations but there are situations where this wasn’t the case. An example of this is when you use coalescing operator. In the current version of C# the following examples are not supported while it is in C# 9.0.

1
2
Person person = student ?? customer; // Shared base type
int? result = b ? 0 : null; // nullable value type

In the current version you always had to cast the objects to the same type to make it work.

Another example, that is less useful in my opinion, is that you can leave out the type after the new keyword when the declaring type is specified. For example:

1
Animal animal = new ("Dog", 4);
My opinion: I think it is a very good addition to C# that it looks more to the context of a statement to determine the type. It just makes it easier for the programmer to do things and it makes the code more readable because less casting is required.

Covariant returns

When you have a base class with a virtual or abstract method that can/must be overwritten, until now you always had to have the same return type in the deriving class. That makes sense because changing the return type would make it a different kind of method. The method signature stays the same but with a different return type you can get into trouble in the code where the method is called. But let’s take a look at the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public abstract class Vehicle
{
    public abstract Fuel GetFuel();
}

public class Car : Vehicle
{
    public override Fuel GetFuel()
    {
        return new Gasoline();
    }
}

In this example the Car class overrides the ‘GetFuel’ method. The implementation of the ‘GetFuel’ method in the Car class always returns a new instance of ‘Gasoline’ (which is a subclass of Fuel). Calling code on a car then always needs to cast the return type to the type ‘Gasoline’ if they want to use it as that type. In C# 9.0 it is possible to override the method and change the returning type to the derived class. The code would look as follows:

1
2
3
4
5
6
7
public class Car : Vehicle
{
    public override Gasoline GetFuel()
    {
        return new Gasoline();
    }
}
When you do this the compiler will understand this and less casting is needed in the calling code.

My opinion: I think this is a good addition because it makes your code more clear and more easy to understand.

Conclusion

C# 9.0 gives us more possibilities to make it easier to create and read code. I think useful features are added and maybe there are some that don’t add a lot (like top-level programs). But the features that don’t add a lot are not features you have to use. Looking forward I think C# is only getting better and easier and that is a positive development!

References: