Table of Contents
Over the past few months, we had the opportunity to thoroughly test the new features of C# 13.0, officially released in November 2024 alongside ASP.NET Core 9.0. Our testing spanned real-world projects and prototypes across various contexts, including enterprise applications, cloud services, and microservices. This hands-on approach allowed us to explore the potential of this version in practical scenarios and evaluate the improvements in terms of productivity, performance, and usability.
C# 13.0 introduces a range of enhancements that simplify writing concise, readable, and high-performing code. Among the highlights are the optimized thread synchronization capabilities, improved support for params collections, and new functionality for object initializers. Furthermore, this version has been designed to further enhance integration with the .NET 9 runtime, enabling developers to leverage the full potential of an increasingly advanced and feature-rich ecosystem.
We explored features such as support for ref variables in asynchronous methods and iterators, as well as improvements to method overload resolution, which proved particularly useful for developing robust and modular APIs. The results were impressive: cleaner code, improved performance in asynchronous operations, and a smoother workflow thanks to tools that simplify managing complex scenarios.
In this article, we’ll walk you through the key innovations introduced in C# 13.0, enriched with practical examples and technical details. Whether you’re building new applications or updating existing projects, you’ll find in C# 13.0 a language equipped with new solutions to tackle the challenges of modern development.
Extended params Collections
In C# 13.0, the params modifier is no longer restricted to arrays. It can now be used with any recognized collection type, including System.Span<T>, System.ReadOnlySpan<T>, and types that implement System.Collections.Generic.IEnumerable<T> with an Add method. This provides greater flexibility in method definitions.
Example:
1 2 3 4 5 6 7 8 9 10 11 12 |
public void Concat<T>(params ReadOnlySpan<T> items) { foreach (var item in items) { Console.Write(item); Console.Write(" "); } Console.WriteLine(); } // Usage Concat("Hello", "World", "!"); |
In this example, the Concat method accepts a variable number of parameters of type ReadOnlySpan<T>, enabling more flexible collection handling.
New Lock Type
The .NET 9 runtime introduces a new thread synchronization type: System.Threading.Lock. This type provides better synchronization through its API. The Lock.EnterScope() method enters an exclusive scope, returning a ref struct that supports the Dispose() pattern to exit the exclusive scope.
Example:
1 2 3 4 5 6 7 8 9 |
var myLock = new Lock(); void CriticalSection() { using (myLock.EnterScope()) { // Code requiring synchronization } } |
In this example, using Lock improves synchronization management compared to the traditional lock statement.
New Escape Sequence
C# 13.0 introduces the \e escape sequence to represent the ESCAPE character Unicode U+001B. Previously, developers used \u001b or \x1b, which could cause ambiguity if followed by valid hexadecimal digits.
Example:
1 |
string escapeSequence = "\e[31mThis is red text\e[0m"; |
This new escape sequence simplifies representing the ESCAPE character in strings.
Natural Type of Method Groups
This feature optimizes overload resolution involving method groups. The compiler now eliminates non-applicable methods in each scope, improving the efficiency of selecting the appropriate method.
Example:
1 2 3 4 |
void PrintMessage(string message) => Console.WriteLine(message); void PrintMessage(Func<string> getMessage) => Console.WriteLine(getMessage()); Action action = PrintMessage; |
Here, the compiler efficiently determines the natural type of the PrintMessage method group.
Implicit Index Access in Object Initializers
The "from the end" index operator (^) is now allowed in object initializer expressions. This allows initializing arrays or collections using negative indices to reference elements from the end.
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class TimerRemaining { public int[] Buffer { get; set; } = new int[10]; } var countdown = new TimerRemaining() { Buffer = { [^1] = 0, [^2] = 1, [^3] = 2, [^4] = 3, [^5] = 4, [^6] = 5, [^7] = 6, [^8] = 7, [^9] = 8, [^10] = 9 } }; |
In this example, the Buffer array is initialized using the ^ operator to access elements from the end.
C# 13.0 also introduces several other advanced features, such as:
- ref and unsafe in Iterator and Async Methods: It’s now possible to declare ref local variables or use unsafe contexts in methods using yield return or async, with restrictions to ensure code safety.
- ref struct in Generic Parameters: ref struct types can now be used as type arguments in generics, expanding API design possibilities.
- Partial Properties and Indexers: Properties and indexers can now be declared as partial within partial types, improving code modularity.
- Overload Resolution Priority: Library authors can designate a preferred overload using the OverloadResolutionPriorityAttribute, improving clarity in method selection.
These improvements make C# 13.0 a significant update, providing developers with more powerful and flexible tools to write efficient, modern code.
For a deeper dive into the new features, we recommend exploring the official Microsoft documentation on C# 13.0.
Useful Links
- Microsoft Learn - What's New in C# 13.0
- Microsoft Learn - What's New in .NET 9
- Microsoft Learn - Official C# Guide
- ASP.NET Core 9.0 Overview
These links will help you further explore the features of C# 13.0 and the .NET 9 ecosystem. If you need additional details or resources, feel free to ask!