What's new in C# 10

C# 10 adds the following features and enhancements to the C# language:

C# 10 is supported on .NET 6. For more information, see C# language versioning.

You can download the latest .NET 6 SDK from the .NET downloads page. You can also download Visual Studio 2022, which includes the .NET 6 SDK.

Note

We're interested in your feedback on these features. If you find issues with any of these new features, create a new issue in the dotnet/roslyn repository.

Record structs

You can declare value type records using the record struct or readonly record struct declarations. You can now clarify that a record is a reference type with the record class declaration.

Improvements of structure types

C# 10 introduces the following improvements related to structure types:

  • You can declare an instance parameterless constructor in a structure type and initialize an instance field or property at its declaration. For more information, see the Struct initialization and default values section of the Structure types article.
  • A left-hand operand of the with expression can be of any structure type or an anonymous (reference) type.

Interpolated string handler

You can create a type that builds the resulting string from an interpolated string expression. The .NET libraries use this feature in many APIs. You can build one by following this tutorial.

Global using directives

You can add the global modifier to any using directive to instruct the compiler that the directive applies to all source files in the compilation. This is typically all source files in a project.

File-scoped namespace declaration

You can use a new form of the namespace declaration to declare that all declarations that follow are members of the declared namespace:

namespace MyNamespace;

This new syntax saves both horizontal and vertical space for namespace declarations.

Extended property patterns

Beginning with C# 10, you can reference nested properties or fields within a property pattern. For example, a pattern of the form

{ Prop1.Prop2: pattern }

is valid in C# 10 and later and equivalent to

{ Prop1: { Prop2: pattern } }

valid in C# 8.0 and later.

For more information, see the Extended property patterns feature proposal note. For more information about a property pattern, see the Property pattern section of the Patterns article.

Lambda expression improvements

C# 10 includes many improvements to how lambda expressions are handled:

  • Lambda expressions may have a natural type, where the compiler can infer a delegate type from the lambda expression or method group.
  • Lambda expressions may declare a return type when the compiler can't infer it.
  • Attributes can be applied to lambda expressions.

These features make lambda expressions more similar to methods and local functions. They make it easier to use lambda expressions without declaring a variable of a delegate type, and they work more seamlessly with the new ASP.NET Core Minimal APIs.

Constant interpolated strings

In C# 10, const strings may be initialized using string interpolation if all the placeholders are themselves constant strings. String interpolation can create more readable constant strings as you build constant strings used in your application. The placeholder expressions can't be numeric constants because those constants are converted to strings at run time. The current culture may affect their string representation. Learn more in the language reference on const expressions.

Record types can seal ToString

In C# 10, you can add the sealed modifier when you override ToString in a record type. Sealing the ToString method prevents the compiler from synthesizing a ToString method for any derived record types. A sealed ToString ensures all derived record types use the ToString method defined in a common base record type. You can learn more about this feature in the article on records.

Assignment and declaration in same deconstruction

This change removes a restriction from earlier versions of C#. Previously, a deconstruction could assign all values to existing variables, or initialize newly declared variables:

// Initialization:
(int x, int y) = point;

// assignment:
int x1 = 0;
int y1 = 0;
(x1, y1) = point;

C# 10 removes this restriction:

int x = 0;
(x, int y) = point;

Improved definite assignment

Prior to C# 10, there were many scenarios where definite assignment and null-state analysis produced warnings that were false positives. These generally involved comparisons to boolean constants, accessing a variable only in the true or false statements in an if statement, and null coalescing expressions. These examples generated warnings in previous versions of C#, but don't in C# 10:

string representation = "N/A";
if ((c != null && c.GetDependentValue(out object obj)) == true)
{
   representation = obj.ToString(); // undesired error
}

// Or, using ?.
if (c?.GetDependentValue(out object obj) == true)
{
   representation = obj.ToString(); // undesired error
}

// Or, using ??
if (c?.GetDependentValue(out object obj) ?? false)
{
   representation = obj.ToString(); // undesired error
}

The main impact of this improvement is that the warnings for definite assignment and null-state analysis are more accurate.

Allow AsyncMethodBuilder attribute on methods

In C# 10 and later, you can specify a different async method builder for a single method, in addition to specifying the method builder type for all methods that return a given task-like type. A custom async method builder enables advanced performance tuning scenarios where a given method may benefit from a custom builder.

To learn more, see the section on AsyncMethodBuilder in the article on attributes read by the compiler.

CallerArgumentExpression attribute diagnostics

You can use the System.Runtime.CompilerServices.CallerArgumentExpressionAttribute to specify a parameter that the compiler replaces with the text representation of another argument. This feature enables libraries to create more specific diagnostics. The following code tests a condition. If the condition is false, the exception message contains the text representation of the argument passed to condition:

public static void Validate(bool condition, [CallerArgumentExpression("condition")] string? message=null)
{
    if (!condition)
    {
        throw new InvalidOperationException($"Argument failed validation: <{message}>");
    }
}

You can learn more about this feature in the article on Caller information attributes in the language reference section.

Enhanced #line pragma

C# 10 supports a new format for the #line pragma. You likely won't use the new format, but you'll see its effects. The enhancements enable more fine-grained output in domain-specific languages (DSLs) like Razor. The Razor engine uses these enhancements to improve the debugging experience. You'll find debuggers can highlight your Razor source more accurately. To learn more about the new syntax, see the article on Preprocessor directives in the language reference. You can also read the feature specification for Razor based examples.