Quick Links

C# 10 was released in November 2021 alongside Visual Studio 2022 and .NET 6. It adds several new features which make it easier and more convenient to work with. Here's a tour of some of the most useful additions and how they'll enhance your workflow.

File-Scoped Namespaces

Let's start with one of the simplest but perhaps most significant changes. You can now apply a namespace to an entire file via a new syntax option for the

        namespace
    

keyword. The remaining code inside the file will be automatically namespaced, even if it's not indented inside a

        namespace
    

block. This works similarly to namespace declarations in PHP.

In C# 9, you needed this code to namespace a class:

namespace Demo {
    

public class Example {

}

}

In C# 10, you can use the following instead:

namespace Demo;
    

public class Example {

}

This saves you some horizontal space in your editor by cutting out a level of indentation. If the file needs to contain multiple classes, you can write them all beginning at column zero - the

        namespace
    

declaration applies to the whole file.

Global Usings

Some namespaces are used very widely across projects. However, you still need to include them manually in each file with a

        using
    

statement.

C# 10 changes this to support a

        global using
    

variant. This will make the referenced namespace accessible across all the files in your project.

global using System;

Global usings can be added to any file that will be included in your compilation. They support all the features of standard

        using
    

statements, including the

        static
    

keyword and aliasing with

        =
    

.

Adopting global usings will cut down the number of lines you write but they're still best reserved for commonly referenced namespaces that present little chance of naming collisions. Beware that relying on a

        global using
    

could make your code more opaque, as new contributors may not immediately realize how a namespaced resource has been included.

A companion feature to global usings is implicit usings. This automatically creates

        global using
    

statements appropriate for your project's type. The capability is turned on by default in the .NET 6 project templates. It can be disabled with the

        ImplicitUsings
    

setting in your

        .csproj
    

file.

Improved Structs

Structs have received several enhancements which bring them into closer parity with classes. These include parameterless constructors, field initializers, full support for

        with
    

expressions, and the option of creating record structs:

public record Point(int X, int Y);

This example creates a "positional" record struct where the

        X
    

and

        Y
    

constructor parameters become implicit public members. You can also manually define members using the existing syntax:

public record struct Point {
    

public int X { get; init; }

public int Y { get; init; }

}

A record struct should be used in scenarios where you need to encapsulate some data without attaching custom behaviors as class methods. A record struct offers integrated value equality checks and features such as

        ToString()
    

. It can be either mutable or immutable via the

        readonly
    

keyword.

Lambda Expression Enhancements

C# 10 adds several improvements to lambda expressions covering their types and syntax. The objective is to make lambdas more akin to regular methods and local functions. Defining one will now be a more familiar experience.

The concept of a "natural" type has been introduced. This lets the compiler infer a lambda's type without manually converting it to a delegate or expression. This creates more readable code when assigning a lambda to a variable:

// C# 9
    

Func<string, int> toInt = (string s) => int.Parse(s);

// C# 10

var toInt = (string s) => int.Parse(s)

The compiler will infer the type of

        toInt
    

as

        Func<string, int>
    

and this will be displayed when you view the code in Visual Studio. Inferences will use

        Func
    

,

        Action
    

, or synthesized delegates.

Natural types will only work when your lambda expression is already fully typed. If you omit parameter types, the compiler won't be able to create a compatible type definition.

A related change is support for explicit return types. Like with a regular function, the return type goes before the lambda's parameter list:

var toInt = int (string s) => int.Parse(s)

Finally, lambdas can now take attributes in the same way as methods and functions. They're positioned at the start of the lambda expression, before the return type and parameter list. You can use attributes to attach extra metadata to your lambdas to facilitate greater introspection and code analysis.

var toInt = [DemoAttribute()] int (string s) => int.Parse(s)

Versatile Deconstructed Assignments

Deconstruction assignments can now both initialize new variables and assign values to existing ones in the same line. Previously you needed to use separate deconstructions for these operations.

In C# 9, this resulted in code that looked like this:

int x = 0;
    

int y = 0;

(x, y) = point;

(int z) = point;

Whereas in C# 10, you can do this:

int x = 0;
    

int y = 0;

(x, y, int z) = point;

Now

        x
    

,

        y
    

, and

        z
    

will all be initialized with values using the single deconstruction assignment, reducing repetition in your code.

Other Changes

String interpolation has been improved and now works with constant strings too. You need to ensure all the strings used to fill your placeholder holes during interpolation are themselves constant values. More broadly, optimizations to the interpolation process may reduce memory use and increase performance through the use of dedicated handlers.

Property patterns have been simplified to provide more readable access to nested properties. You can now use dot syntax to access nested property values, instead of multiple layers of parentheses:

// C# 9
    

{ Train: {ArrivalTime: "10:00"} }

// C# 10

{ Train.ArrivalTime: "10:00" }

Elsewhere, compiler optimizations mean you'll benefit from fewer false positives during definite assignment and null-state checks. Several C# 9 issues which triggered spurious warnings at compile time have been addressed, resulting in more accurate checks that are better equipped to help you debug issues that actually matter. The problems were linked to using null coalescing expressions and variable comparisons against boolean constants.

Conclusion

C# 10 adds several new capabilities which will help to make development more straightforward. However, many other new features including the

        field
    

keyword and required properties have been pushed back to the next major release. Generic attributes and static abstract members for interfaces made it into 10 but with the preview tag still attached.

You can start using C# 10 today by downloading Visual Studio 2022. The language is also available as part of the standalone .NET 6 SDK which works across Windows, Mac, and Linux.