What's New in C# 9

 

In this post we’ll explore the new features introduced in C# 9. To use C# 9 in any of our projects, we need to install the .NET 5 preview version available for download on the Microsoft page.

We should specify in our .csproj file that it uses this particular version:

<Project>
 <PropertyGroup>
   <LangVersion>preview</LangVersion>
   <TargetFramework>net5.0</TargetFramework>
 </PropertyGroup>
</Project>

And finally, mark the option Tools-> options ->environments ->preview features. There you’ll find the option use previews of .NET Core SDK and then restart Visual Studio.

 

 

1 - Init-only Properties

With init-only properties, we can create object initializers with immutable properties.

To do this, we create a class, in our example Car, but instead of creating getters and setters or assigning values from the constructor, we do it like this:

public class Coche
{
    public string Marca { get; init; }
    public string Modelo { get; init; }
}

As you can see, we don’t have a setter anymore. Instead, we have a new keyword called init. This way, we can use it as object initializers in C# 9.

Coche coche1 = new Coche { Marca = "Opel", Modelo = "Vectra" };
Coche coche2 = new Coche { Marca = "Audi", Modelo = "A3" };

Console.WriteLine($"Mi primer coche fue un {coche1.Marca} {coche1.Modelo}");
Console.WriteLine($"Mi segundo coche fue un {coche2.Marca} {coche2.Modelo}");

 

1.1 - Readonly Fields with Init Accessors

When we use init accessors, they can only be used while initializing the object. This means they offer functionality similar to readonly. In fact, if we try to assign a value to a field with init, it will tell us it is read-only.

 

For this reason, we can mutate readonly fields, as long as we do so from the init. Previously, we were only able to change the values of readonly fields from the constructor, but now we can do it from the init field.

We must change our class as follows:

public class Coche
{
    private readonly string marca;
    private readonly string modelo;

    public string Marca
    {
        get => marca;
        init => marca = (value ?? throw new ArgumentNullException(nameof(marca)));
    }
    public string Modelo
    {
        get => modelo;
        init => modelo = (value ?? throw new ArgumentNullException(nameof(modelo)));
    }
}

 

 

2 - Records in C#

In C#9, we’ll have the record structure, which is a type (value type) that allows us to encapsulate the state of a property.

This means that the object or the information contained in a record will be immutable.

 

To create a record, we just need to change our class as follows:

public record Coche
{
    public string Marca { get; init; }
    public string Modelo { get; init; }
}

 

As you can see, we’ve only changed the type, but what's the point? Using records lets us use new features; the main idea behind a record is for it to be immutable. Therefore, if we want to modify a record, what we need to do is make a copy of it. That’s where this feature makes sense.

 

2.1 - The with Expression in Records

With records, we can use a new way to create new values from existing ones by making a copy and then modifying what we need.

To make a copy, we need to use the with keyword as follows:

Coche coche1 = new Coche { Marca = "Opel", Modelo = "Vectra" };
Coche cochedeSustitucion = coche1 with {Modelo = "Astra"};

Console.WriteLine($"Mi primer coche fue un {coche1.Marca} {coche1.Modelo}");
Console.WriteLine($"El coche de sustitución fue {cochedeSustitucion.Marca} {cochedeSustitucion.Modelo}");

 

As you can see, we use the object we want to copy followed by the with keyword and then the modification we want to implement.

Records also support inheritance, while struct for example, does not support it.

 

2.2 - Positional Records

When using records, we can implement positional constructors and deconstructors, meaning the variables are assigned in the order they are defined.

Going back to our Car record example:

public record Coche
{
    public string Marca { get; init; }
    public string Modelo { get; init; }

    public Coche(string marca, string modelo) 
      => (Marca, Modelo) = (marca, modelo);
}

We see that we have our constructor, but it’s a bit different from a regular constructor. Not only does it have a lambda expression, which is not usual, but the values are assigned in a peculiar way.

(Marca, Modelo) = (marca, modelo);

As mentioned, the values are assigned according to position. So Marca will be assigned from marca and Modelo from modelo. With the usual result in our console.Writeline:

Mi primer coche fue un Opel Vectra

Of course, we must change the object instantiation as follows:

Coche coche1 = new Coche("Opel", "Vectra")

 

This means if we change the order of Marca and Modelo in the value assignments, they will be assigned differently:

(Modelo, Marca) = (marca, modelo);
//Result:
Mi primer coche fue un Vectra Opel

 

 

3 - Pattern Matching Improvements

As we know, in C# 8, the switch statement received improvements, but that was not enough. The C# language development team wanted to further improve the syntax to make it easier to understand when reading the code.

 

For this example, I added the CV property to the record.

public record Coche
{
    public string Marca { get; init; }
    public string Modelo { get; init; }
    public int CV { get; init; }

    public Coche(string marca, string modelo, int cv) 
      => (Marca, Modelo, CV) = (marca, modelo, cv);
}

 

3.1 - Relational Patterns

With relational patterns, we can use < > for less than and greater than:

static int CalcularSeguro(Coche coche) =>
  coche.CV switch
    {
        > 100 => 1000,
        < 20 => 100,
        _ => 500,
    };

 

3.2 - Logical Patterns

With logical patterns, we can use the keywords and, or, or not, which correspond to `and`, `or`, and `not` negation.

static int CalcularSeguro(Coche coche) =>
  coche.CV switch
    {
        > 100 => 1000,
        < 20 and > 10 => 100,
        _ => 500,
    };

This way, we can increase the expressiveness of our expressions.

 

 

4 - Improvements in the new Expression

As we remember from C# 8, if we specify the variable type when instantiating an object, we don’t need to indicate the type because the compiler understands it from the variable type.

Coche coche1 = new ("Opel", "Vectra", 90);

 

Unfortunately, this feature wasn’t available for data collections, like lists in C# 9, but now it is.

var coches = new List<Coche>
{
    new ("Opel", "Vectra", 90),
    new ("Opel", "Astra", 85),
    new ("Audi", "A3", 130),
};

 

 

5 - Top-level Programs

The C# development team seems to be taking the language a bit closer to other scripting languages, like Python.

What does this mean?

 

This is a typical structure for a console application in C# when you first open the editor, with a Hello World:

using System;

namespace CSharp9Ejemplo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hola Mundo");
        }
    }
}

 

As you can see, we don’t just have our hello world statement, but also extra information, like the namespace, the class, the main method, and even the arguments passed as parameters.

This information is often unnecessary, especially when we’re learning. The C# team noticed this and wanted to address it by introducing top-level programs. These let you remove all the unnecessary boilerplate and keep just what you need: the using statement and the Console.Writeline() statement.

using System;

Console.WriteLine("Hola Mundo");

 

The compiler understands what we want and takes care of everything else behind the scenes, keeping it transparent for the user.

 

This feature is very useful when writing small scripts. However, it’s not limited to small tasks, you can use any language library, for example Threading.Task, to make concurrent HTTP requests.

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café