What's New in C# 9? A Guide to the Key Features

A deep dive into the major features introduced in C# 9 with .NET 5, including records, top-level statements, and improved pattern matching. Learn how these additions can make your code more concise and expressive.

C# 9, released alongside .NET 5, brought a host of powerful new features to the language, with a strong focus on simplifying code and enabling more modern, data-centric programming patterns. The additions of records and top-level statements, in particular, have fundamentally changed how developers can write C#.

Let's explore the most impactful features of C# 9.

1. Records: The New Way to Model Data

This is the headline feature of C# 9. Records are a new reference type that provides a simplified syntax for creating immutable data objects. The compiler automatically generates several boilerplate methods for you, making them perfect for DTOs (Data Transfer Objects) and other data-centric classes.

The Old Way:

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }

    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
    // Plus manual implementation of Equals, GetHashCode, ToString...
}

The New Way with Records:

public record Person(string FirstName, string LastName);

With this single line, the compiler generates:

  • A constructor.
  • init-only properties (see below).
  • Value-based equality (Equals and GetHashCode are implemented for you).
  • A clean ToString() implementation.
  • A deconstructor.

Records also support a non-destructive mutation syntax called with expressions:

var person1 = new Person("John", "Doe");
var person2 = person1 with { LastName = "Smith" }; // Creates a new record

2. Init-Only Setters

Closely related to records, init-only setters allow you to create immutable properties that can only be set during object initialization. This is a great alternative to making properties read-only and forcing all initialization through the constructor.

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

// You can set the properties in an object initializer
var person = new Person { FirstName = "Jane", LastName = "Doe" };

// But you can't change them later (this would be a compile error)
// person.FirstName = "Janet";

3. Top-Level Statements

This feature dramatically simplifies the code needed for simple programs and utilities. You can now write your executable code directly in your Program.cs file, without needing to manually define a Program class and a Main method.

The Old Way:

using System;

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

The New Way:

// Program.cs
using System;

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

The compiler automatically generates the Program class and Main method for you behind the scenes. This makes C# a much more approachable language for beginners and is fantastic for writing simple scripts.

4. Improved Pattern Matching

C# 9 continues to enhance pattern matching with several new patterns:

  • Relational Patterns: You can now use relational operators (<, >, <=, >=) in patterns.
    public string GetGroup(int age) => age switch
    {
        < 18 => "Child",
        >= 18 and < 65 => "Adult",
        >= 65 => "Senior"
    };
    
  • Logical Patterns: You can combine patterns with and, or, and not.
    if (c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z'))
    {
        // It's a letter
    }
    

Conclusion

C# 9 was a significant release that focused on making C# more concise, expressive, and easier to learn. Records have provided a much-needed syntax for data-focused programming, while top-level statements have lowered the barrier to entry for new developers. These features have laid the groundwork for the even more simplified syntax that would come later in C# 10's Minimal APIs.