Joydip Kanjilal
Contributor

The best new features in C# 13

how-to
Aug 01, 202410 mins
C#Development Libraries and FrameworksMicrosoft .NET

C# 13 introduces several new features that make it easier to build modern applications including enhanced params collections and a new Lock API that improves thread synchronization.

Winner holding golden trophy cup above head sun and golden sky in background
Credit: afotostock / Shutterstock

Arriving with .NET 9 in November, C# 13 brings a plethora of new features and enhancements that make it easier to write efficient, high performant code. The params keyword has been extended to work with any collection type. A new Lock type improves thread synchronization. You can now use local variables of ref or ref struct type in asynchronous and iterator methods. New partial properties allow you to separate the declaration of properties from implementation code. C# 13 also adds neat tweaks to escapes and index access.

Just as we walked through the new features and enhancements in C# 12 last fall, in this article we’ll take a close look at these key new features in C# 13. To work with the code examples provided in this article, you should have Visual Studio 2022 Preview installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 Preview here.

Create a console application project in Visual Studio 2022 Preview

First off, let’s create a .NET Core 9 console application project in Visual Studio 2022 Preview. Assuming you have Visual Studio 2022 Preview installed, follow the steps outlined below to create a new .NET Core 9 console application project.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Click Next.
  7. In the “Additional information” window shown next, choose “.NET 9.0 (Preview)” as the framework version you would like to use.
  8. Click Create.

We’ll use this .NET 9 console application project to work with the new C# 13 features in the subsequent sections of this article.

Enhanced params collections

Prior to C# 13, the params keyword was confined to array types only. With C# 13, you can now use the params keyword to work with any collection type, such as System.Span<T> and System.ReadOnlySpan<T>, as well as types that implement the System.Collections.Generic.IEnumerable<T> interface.

When you declare a parameter using the params keyword, i.e. whenever params precedes a parameter of a method, you can use a comma delimited list of values as shown in the code snippet below.

void Display(params IEnumerable<string> authors)
   => Console.WriteLine(String.Join(", ", authors));

Moreover, if you call a method that accepts an IEnumerable<T> as a parameter, you can pass the result of a LINQ expression to your method. For example, consider the following class.

public class Author
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

The code snippet below illustrates how we can pass the result of a LINQ expression to a method that accepts an IEnumerable<T>.

void Display(params IEnumerable<string> authors)
   => Console.WriteLine(String.Join(", ", authors));
var authors = new List<Author>
{
   new Author{Id = 1, FirstName = "Joydip", LastName = "Kanjilal"},
   new Author{Id = 2, FirstName = "Steve", LastName = "Jones"},
   new Author{Id = 3, FirstName = "Michael", LastName = "Stevens"}
};

Suppose you have a method that accepts a params argument with the following signature.

public void AddData(params Author[] authors) {  //Usual code  }

When you’re using C# 13, the following additional overloads will be added automatically.

public void AddData(params IEnumerable<Author> authors) {  //Usual code  }
public void AddData(params IReadOnlySpan<Auhor> authors) {  //Usual code  }

What are the benefits of these overloads? By adding an IEnumerable overload, support for LINQ is enabled. And by adding a ReadOnlySpan<T> or Span<T> overload, memory allocations can be reduced, enhancing performance. You can read more about this in Microsoft’s documentation.

Finally, note that per the official blog, several methods pertaining to the .NET runtime have been updated to accept params Span<T> in order to reduce memory allocations and improve performance. For example, the AppendJoin method given below has been updated to improve performance.

public StringBuilder AppendJoin(string? separator, params ReadOnlySpan<string?> values)

New Lock object

.NET 9 introduces a new thread synchronization type called System.Threading.Lock, which provides enhanced thread synchronization capabilities. Note that the runtime will detect whether the target of a lock is a Lock object, in which case it will use the updated API in lieu of the traditional API that uses System.Threading.Monitor. The compiler also will recognize if you convert a Lock object to some other type where System.Threading.Monitor code would be generated.

Let’s compare the old and new APIs. The code snippet below implements synchronization using the traditional API.

public class DbManager
{
    private object objLock = new object();
    public void InsertData()
    {
        lock (objLock)
        {
            //Your usual code to insert data to the database
        }
    }
}

To use the new API, you need to change only one line of code:

private System.Threading.Lock objLock = new System.Threading.Lock();

Hence, the updated DbManager class would have the following code.

public class DbManager
{
    private System.Threading.Lock objLock = new System.Threading.Lock();
    public void InsertData()
    {
        lock (objLock)
        {
            //Your usual code to insert data to the database
        }
    }
}

Using ref and unsafe in async methods and iterators

Before C# 13, declaring local ref variables or local variables of a ref struct type in either async methods or methods that use yield return, commonly known as iterator methods, was not possible. Nor could these methods have an unsafe context. C# 13 allows the declaration of ref local variables and local variables of a ref struct type in asynchronous methods. Similarly, C# 13 allows unsafe contexts in iterator methods.

You can also safely use types like System.ReadOnlySpan<T> in asynchronous and iterator methods. The compiler will tell you if you violate safety rules.

The primary benefit of using a ref struct is to get the benefit of pointers in a safe context and prevent memory allocation in several common scenarios. Hence, by using ref struct types in async methods and iterators, you can improve the performance of your application. This is the most useful new feature in C# 13 in my opinion.

C# 13 allows you to make a type argument a ref struct by specifying allows ref struct in the where clause of the type parameter. Note that if you use a type parameter specified with allows ref struct, then you are including all of the behaviors and restrictions of the ref struct type.

Consider the following piece of code that shows a ref struct and a method that uses it as a parameter.

ref struct TestStruct
{

}

TestStruct TestMethod<TestStruct>(TestStruct p)
    where TestStruct : allows ref struct
    => p;

The following code snippet illustrates how ref struct instances can be created and used inside an async method.

async Task<int> Test()
{
    TestStruct testStruct = new TestStruct();
    TestMethod(testStruct);
    return await Task.FromResult(0);
}

Partial properties

Partial properties, like partial methods, are a new feature added in C# 13. They support source generators and are used to separate the declaration of a property from its implementation code. Note that partial methods were introduced earlier and gained some traction in C# 9.

The following code snippet illustrates how partial properties can be used:

partial class MyClass
{
    string myField;
    public partial string MyProperty { get; set; }
    public partial string MyProperty
    { get => myField; set => myField = value; }
}

It should be noted that when you declare a partial property with accessors having semicolon bodies without any implementation code inside, it is assumed to be a defining declaration. By contrast, when a partial property has accessors that contain implementation code, it is considered to be an implementing declaration.

Partial properties are used to provide support for source generators. Their sole purpose is to isolate the declaration of a property from its implementation code.

Easy escape sequence

With C# 13, a new escape sequence has been introduced that makes specifying escapes easier. Prior to C# 13, you could specify the ESC character by using the Unicode character literal as shown below.

char esc = '\u001B';

With C# 13, you can specify the ESC character much more concisely as shown in the following code snippet:

char esc = '\e';

Implicit index access

With C# 13, the implicit “from the end” index operator ^ can now be used in object initializers. You can use ^ to specify a position in a collection that is relative to the end of the collection.

For example, consider the following class.

class InitializerDemo
{
    public int[] integers { get; set; } = new int[5];
}

You can now use the following piece of code in C# 13 to take advantage of the index operator.

var arr = new InitializerDemo
{
    integers =
    {
        [0] = 100,
        [^1] = 1000
    }
};

When you execute the above program, arr.Integers[0] will have the value 100 while arr.Integers[4] will have the value 1000. You can use the following line of code to display the values of the integer array at the console.

Console.WriteLine("The value of arr.Integers[0] is {0} and arr.Integers[4] is {1}", arr.Integers[0], arr.Integers[4]);

Figure 2 shows the output at the console when the code is executed.

c sharp 13 index

Figure 2. The implicit “from the end” index operator ^ in action. 

IDG

TargetFramework .NET 9

Note that you will need to have .NET 9 installed in your computer to work with C# 13. If you want to change your existing projects to use C# 13, you will need to set the TargetFramework to .NET 9 as shown in the code snippet given below.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
        <LangVersion>preview</LangVersion>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

The new features and enhancements in C# 13 outlined in this article will give you more flexibility and help you write cleaner, more maintainable C# code. To explore the new features of C# 13, you should download the latest preview of Visual Studio 2022 with .NET 9. You can learn more about the new features in C# 13 here and here.

Joydip Kanjilal
Contributor

Joydip Kanjilal is a Microsoft Most Valuable Professional (MVP) in ASP.NET, as well as a speaker and the author of several books and articles. He received the prestigious MVP award for 2007, 2008, 2009, 2010, 2011, and 2012.

He has more than 20 years of experience in IT, with more than 16 years in Microsoft .Net and related technologies. He has been selected as MSDN Featured Developer of the Fortnight (MSDN) and as Community Credit Winner several times.

He is the author of eight books and more than 500 articles. Many of his articles have been featured at Microsoft’s Official Site on ASP.Net.

He was a speaker at the Spark IT 2010 event and at the Dr. Dobb’s Conference 2014 in Bangalore. He has also worked as a judge for the Jolt Awards at Dr. Dobb's Journal. He is a regular speaker at the SSWUG Virtual Conference, which is held twice each year.

More from this author