What's new in C# 10? A comprehensive overview of the latest features

C# 10.0 introduced several new features, including global usings, file-scoped namespaces, improved support for interpolated strings, improved support for lambda expressions, and more.

Here are some of the new features introduced in C# 10.0 with sample code:

  1. File-scoped namespaces

With file-scoped namespaces, you can define a namespace that applies to all code in a file, rather than having to add a namespace declaration to every class in the file. Here's an example:


// MyFile.cs
namespace MyNamespace;

class MyClass
{
    // ...
}

  1. Global using directives 

With global using directives, you can import a namespace across the entire project, without having to add a using directive to every file that needs it. Here's an example:


// Program.cs
global using System;
global using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List myList = new();
        myList.Add(1);
        myList.Add(2);
        Console.WriteLine(myList.Count);
    }
}

 

  1. Interpolated string improvements

In C# 10.0, you can now use interpolated strings as format strings for string.Format, and you can specify the format string using a new : syntax. Here's an example:


int myNumber = 42;
string myString = $"{myNumber:0000}";
Console.WriteLine(myString); // outputs "0042"

 

  1. Improved lambda expressions

C# 10.0 introduces several improvements to lambda expressions, including the ability to use the and and or keywords instead of && and ||. Here's an example:


List myList = new() { 1, 2, 3, 4, 5 };
List evenNumbers = myList.Where(x => (x % 2 == 0) and (x > 2)).ToList();
Console.WriteLine(string.Join(",", evenNumbers)); // outputs "4"

 

  1. Lambda expressions with block bodies

C# 10.0 allows you to use block bodies with lambda expressions. This can make your code more readable, especially for longer expressions. Here's an example:


Func<int, int> myFunc = x =>
{
    int y = x * 2;
    return y + 1;
};
Console.WriteLine(myFunc(5)); // outputs "11"

 

  1. Improved support for pattern matching

C# 10.0 introduces several improvements to pattern matching, including the ability to use patterns in more places, such as in switch statements and in is expressions. Here's an example:

int? myNumber = 42;
if (myNumber is int { } value)
{
    Console.WriteLine($"The value is {value}.");
}
else
{
    Console.WriteLine("The value is null.");
}

 

  1. static abstract members

C# 10.0 allows you to declare static abstract members in abstract classes or interfaces. This can be useful if you want to provide a default implementation for a static member, but still allow derived types to override it. Here's an example:


public abstract class MyBaseClass
{
    public static abstract int MyStaticMethod();
}

public class MyDerivedClass : MyBaseClass
{
    public static int MyStaticField = 42;
    public static override int MyStaticMethod() => MyStaticField;
}

Console.WriteLine(MyDerivedClass.MyStaticMethod()); // outputs "42"

 

  1. with expressions for record types

C# 10.0 introduces the ability to use the with keyword to create a new instance of a record type with some of its properties changed. Here's an example:

record MyRecord(string Name, int Age);

var myRecord = new MyRecord("Alice", 25);
var myNewRecord = myRecord with { Age = 30 };
Console.WriteLine(myNewRecord); // outputs "MyRecord { Name = Alice, Age = 30 }"

 

  1. Extended property patterns

C# 10.0 extends the support for property patterns, allowing you to match on the type of the property, as well as its value. Here's an example:


public class MyClass
{
    public int MyProperty { get; set; }
}

var myObject = new MyClass { MyProperty = 42 };
if (myObject is { MyProperty: > 0, MyProperty: int myValue })
{
    Console.WriteLine($"The value is {myValue}.");
}

 

  1. Implicit new() constraint for type parameters

C# 10.0 allows you to use the new() constraint implicitly, without having to specify it explicitly in the type parameter list. Here's an example:


public class MyClass where T : new()
{
    public void DoSomething()
    {
        T myObject = new();
        // ...
    }
}

 

  1. async method return types

C# 10.0 introduces the ability to use any type as the return type of an async method, instead of being limited to Task, Task<T>, or ValueTask<T>. This can be useful if you want to return a custom type from an async method. Here's an example:


public async MyCustomType MyAsyncMethod()
{
    // ...
}

 

  1. Improved support for interpolated verbatim strings

C# 10.0 introduces several improvements to interpolated verbatim strings, including the ability to use # to escape curly braces in the format string. Here's an example:


string myString = @"First line
Second line
Third line: {myNumber:#,##0}";
int myNumber = 123456789;
Console.WriteLine(myString); // outputs "First line\nSecond line\nThird line: 123,456,789"

 

  1. Mixed assignment and declaration

C# 10.0 introduces the ability to mix declaration and assignment in a single statement, similar to how you can do in JavaScript or TypeScript. Here's an example:


int myNumber = 42, yourNumber = 7;
Console.WriteLine($"{myNumber}, {yourNumber}"); // outputs "42, 7"

 

  14. Improved support for const fields

C# 10.0 introduces several improvements to const fields, including the ability to initialize them with non-constant expressions, and the ability to use them in attributes. Here's an example:


public class MyClass
{
    public const int MyConstant = 42;
    public const string MyString = "Hello, world!";
}

[Obsolete(MyClass.MyString)]
public void MyMethod()
{
    // ...
}

 

15. Improved support for default expressions

C# 10.0 introduces several improvements to default expressions, including the ability to use them in attribute arguments, and the ability to use them with the is and as operators. Here's an example:


[DefaultValue(default(int?))]
public int? MyNullableInt { get; set; }

object myObject = null;
if (myObject is default(int))
{
    Console.WriteLine("This won't be executed.");
}

up