C# 13 - New features and how you can use them now!

Published: Monday 18 November 2024

C# 13 has been launched as part of the .NET 9 release and we'll take a look at the new features that you can use right now.

As well as that, we'll guide you on how you start using C# 13, even if you don't want to update your ASP.NET Core application to .NET 9.

New features

Here are the new features that are included in C# 13:

params collections

Traditionally, you could only use the params modifier on arrays. But this has been extended to a range of collection types, such as a Span, a ReadOnlySpan and any collection type that implements the IEnumerable interface and has an Add method.

C# coding challenges

C# coding challenges

Our online code editor allows you to compile the answer.

This example uses the params modifier with a List and returns it.

public static class ParamsCollections
{
	public static List<int> ReadMyNumbers(params List<int> numbers)
	{
		return numbers;
	}
}

New lock object

There is a new lock type in C# which can be found in System.Threading.Lock. Microsoft claim that it "provides better thread synchronisation through its API".

The lock statement recognises if it uses the new Lock object based on the instance type that is passed in as the parameter. If it's using the newLock object, the updated API is used rather than the traditional API which uses System.Threading.Monitor.

Here is an example of the new Lock object:

public static class NewLock
{
	public static Lock _lock = new Lock();

	public static async Task EnteringMyLock()
	{
		var threadId = Thread.CurrentThread.ManagedThreadId;
		Console.WriteLine($"Thread ID: {threadId} - Waiting for lock to open");

		lock (_lock)
		{
			Console.WriteLine($"Thread ID: {threadId} - Entered the lock");
			Console.WriteLine($"Thread ID: {threadId} - Exiting the lock");
		}

		await Task.CompletedTask;
	}
}

New escape sequence

Before C# 13, the escape sequence would be written as \u001b. You could also write it as \x1b although that wasn't recommended.

Now you can simply write it as \e.

Implicit index access

The "from the end" index operator ^ is now allowed in object initialiser expressions.

In this example, we have created an ImplicitIndexAccess class with a Numbers property which has an integer array with a length of 5.

We can now initialise the Numbers property when we initalise ImplicitIndexAccess class and use the "from the end" index operator to populate the array values.

public class ImplicitIndexAccess
{
	public int[] Numbers { get; set; } = new int[5];
}

var implicitIndexAccess = new ImplicitIndexAccess()
{
	Numbers =
	{
		[^1] = 5,
		[^2] = 4,
		[^3] = 3,
		[^4] = 2,
		[^5] = 1
	}
};

^1 means the last one, ^2 means the second to last one, and so on. So the Numbers property array will be populated as 1, 2, 3, 4, 5.

ref and unsafe in iterators and async methods

Before C# 13, iterator methods (methods that use yield return) and async methods couldn't declare local ref variables, nor could they have an unsafe context.

But now they can. However, these variables can't be accessed across an await boundary or a yield return boundary.

Here is an example of creating a local reference using the MyMessage static string and appending "Hello there" to the message.

public static class RefInIteratorsAndAsyncMethods
{
	public static string MyMessage = string.Empty;

	public static async Task AddMessage()
	{
		await Task.Delay(TimeSpan.FromMilliseconds(500));

		ref var myMessage = ref GetMyMessage();
		AddToMyMessage(ref myMessage, "Hello there");
	}

	private static ref string GetMyMessage()
	{
		return ref MyMessage;
	}

	private static void AddToMyMessage(ref string message, string theMessage)
	{
		message += theMessage;
	}
}

allows ref struct

ref struct types can now be declared as a generic type or method. You have to add allows ref struct in the generic where clause for this to work as this example demonstrates.

public class AllowsRefStruct<TRef> where TRef : allows ref struct
{
}

public ref struct IsValid
{
	public bool Valid { get; set; }
}

ref struct interfaces

Staying on the theme of ref struct types, they can now implement interfaces as this example proves.

public ref struct RefStructInterfaces : IInterface
{
}

public interface IInterface { }

Partial properties and indexers

There are more partial members with the introduction of partial properties and indexers.

For this to work, you have to add the declaration into one of the partial classes and the implementation into another partial class with the same name and namespace.

public partial class MorePartialMembers
{
	// Declaration
	public partial string? Surname { get; set; }

	public partial string this[int x] { get; set; }
}

public partial class MorePartialMembers
{
	// Implementation
	private string? _surname;
	public partial string? Surname
	{
		get => _surname;
		set => _surname = value;
	}

	public partial string this[int x] { get => "Hello"; set { } }
}

Partial members are useful in code generation tools where the tool will declare the members and you can add your own implementation. Read more about how partial classes work.

How to use C# 13

To use C# 13, you'll need to download the .NET 9 SDK. From there, you can either update your .NET application to .NET 9, or set the LangVersion attribute in your .csproj file.

Not updating to .NET 9

If you don't want to update your application to .NET 9, you can either set the LangVersion attribute to either latest, latestMajor, default, or specify the C# version number which is 13.0.

Here's an example of a .NET 8 project using C# 13:

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net8.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<LangVersion>13.0</LangVersion>
	</PropertyGroup>

</Project>

Not all features will work below .NET 9

If you are not updating your application to .NET 9, then the new lock object will not work. This is because the Lock object is in the .NET 9 source code.

Additionally, you can't use a ref struct as a generic type below .NET 9 as it's not supported in the runtime.

Watch the new features demonstration

Watch our new features demonstration video where we go through each new feature and show you how to implement them.

You can also download the code example which contains each of these new features for you to try.