Skip to main content

3 New features in C# 9

·5 mins

.NET 5 was released few weeks ago, and with it we got a new release of C# 9 packed with a bunch of new features, Let’s go over 3 of them, see how to use them and identify where they are useful and where you may not want to be using them.

1. Top-level Programs #

Prior to C# 9 a basic and simple Hello World Console application would look like this:

using System;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

12 Lines of boilerplate code, whereas the feature is in the one line that write the Hello World!

In C# 9 you can remove all these ceremony, and just write the main program at the top level instead:

using System;

Console.WriteLine("Hello World!");

You can even use Async methods and pass-in Args

using System;
using System.IO;
using System.Threading.Tasks;

Console.WriteLine($"Writing {args[0]} to simple.txt");
await WriteToSimpleTxt(args[0]);

async Task WriteToSimpleTxt(string text)
{
    await File.WriteAllTextAsync("simple.txt", text);
}

Note that the method WriteToSimpleTxt is a local method and cannot have a modifier, if you put public in front of it you are going to get yelled at by your editor (and compiler)

Top-level methods are local functions and can’t have a modifier

Why would you need to use Top-level Programs ?

First of all, you don’t have to use top-level programs as you can still use the usual syntax with namespace, class and Main method.

I think the main usage of top-level program is when you need something simple, quick with less boilerplate, less curly-braces and indentation.

2. Init-only properties #

In some cases we want to make a class immutable; only allow setting its state in the constructor and not be able to change it afterwords. Using Read-only auto properties introduced in C# 6 we usually write something like this:

public class Article
{
    public Article(string title)
    {
        Title = title;
    }

    public string Title { get;}
}

Or we can use a read-only field

public class Article
{
    private readonly string _title;

    public Article(string title)
    {
        _title = title;
    }

    public string Title => _title;
}

The downside of this is that we can’t use the nice Object Initializer syntax when constructing a new instance while keeping the class immutable, so if we write this :

Property without setter can’t use object initializer syntax

we get an error, and if we want to get rid of it, we would add a setter to the Title, and that would make the object mutable from everywhere.

Comes init-only properties:

public class Article
{
    public string Title { get; init; }
}

Now we can use object initializer to create a new instance:

  var article = new Article
  {
      Title = "C# 9 Init-only properties"
  };

Note that if you try to set the value of Title afterword you get an error

Init-only property cannot be assigned

Why would you need to use Init-only Properties ?

Init-properties allow to create immutable properties without having to define a constructor. It’s an explicit way to express the intent from the code rather than have some type of unwritten rule that people have to follow.

3. Target typing #

“Target typing” is a term used for when an expression gets its type from the context of where it’s being used.

Prior to C# 9 when you instantiate a type and assigned it to a variable you would write like this:

//Person.cs
public class Person
{
    public string Name { get; }

    public Person(string name)
    {
        Name = name;
    }
}

//Program.cs
Person p = new Person("Bleu");

You can use the keyword var that was introduced in C# 3.0

var p = new Person("Bleu");

As you can see the new expression in C# have always required a type to be specified, with C# 9 you can leave out the type if there’s a clear type that the expression is assigned to, which is the case when you don’t use var

Person p = new("Bleu");

Why would you need to use this ?

With var we were able to reduce the amount of code that needed to be written for local variable, but for fields and properties we still had to write the type on both sides of the = , with this syntax we can omit the type on the right side if it can clearly be inferred.

// Prior to C# 9.0
public class ClassRoom
{
    public ClassRoom()
    {
        Students = new List<Person>();
    }
    public List<Person> Students { get; } = new List<Person>();
}

// With C# 9.0
public class ClassRoom
{
    public ClassRoom()
    {
        Students = new ();
    }
    public List<Person> Students { get; }
}

// You can also instantiate it directly at declaration
public class ClassRoom
{
    public List<Person> Students { get; } = new ();
}

Note that the generic <Person> argument is not needed with the target-typed new expression, you don’t need to write new<Person>()

But only reducing the amount of code isn’t always a good idea, if you need extra thinking or hovering over the code to see what the new will create I would not use this feature, for example in the constructor earlier you would have to look into two places : the declaration and the instantiation to know what the new is going to create.

public class ClassRoom
{
    public ClassRoom()
    {
        Students = new (); // It's not obvios what this line will create
    }
    public List<Person> Students { get; }
}

That’s it for this post, in the next one I will go through one of my favorite new features of C# : Records