.NET 9 and C# 13 were released on November 12, 2024. In this blog post, I want to show you a list of new features in C# 13.
1. Params Collections
Previously, when using params
keyword you were able to use only arrays:
csharpPrintNumbers(1, 2, 3, 4, 5); public void PrintNumbers(params int[] numbers) { // ... }
C# 13 introduces Params Collections, allowing you to use the following concrete types:
- Arrays
- IEnumerable<T>
- List<T>
- Span<T>
For example, you can use a list:
csharpList<int>numbers = [ 1, 2, 3, 4, 5 ]; PrintNumbers(numbers); public void PrintNumbers(params List<int> numbers) { // ... }
And further in PrintNumbers
method you can use, for example, LINQ methods over the params collection.
2. New Lock Object
System.Threading.Lock
is a new thread synchronization type in .NET 9 runtime.
It offers better and faster thread synchronization through the API.
How it works:
- Lock.EnterScope() method enters an exclusive scope and returns a ref struct.
- Dispose method exits the exclusive scope
When you replace object
with a new Lock
type inside a lock
statement, it starts using a new thread synchronization API.
Rather than using old API through System.Threading.Monitor
.
csharppublic class LockClass { private readonly System.Threading.Lock _lockObj = new(); public void Do(int i) { lock (_lockObj) { Console.WriteLine($"Do work: {i}"); } } }
3. Partial Properties and Indexers
C# 13 adds support for partial Properties
and Indexers
.
Partial Properties and Indexers let you split the logic for getters, setters, and index accessors across multiple files.
This feature can be useful particularly for source generators.
csharppublic partial class C { // Declaring declaration public partial string Name { get; set; } } public partial class C { // implementation declaration: private string _name; public partial string Name { get => _name; set => _name = value; } }
4. Overload Resolution Priority
With Overload Resolution Priority, you can select the exact order of methods that will be shown in the code editor suggestions.
csharpusing System.Runtime.CompilerServices; public static class MappingExtensions { [OverloadResolutionPriority(1)] public static Product ToResponse(this Product entity, Result result) => new Product(...); [OverloadResolutionPriority(0)] public static Product ToResponse(this Product entity) => new Product(...); }
5. Implicit Index Access
C# 13 now allows using "from the end" index operator (^) in an object initializer expression.
For example, you can now initialize an array that counts down from 4 to 0:
csharpvar countdown = new TimerRemaining() { buffer = { [^1] = 0, [^2] = 1, [^3] = 2, [^4] = 3, [^5] = 4 } };
In previous C# versions, you were not able to use the ^ operator in object initializers.
6. New Escape Sequence
In previous C# versions you needed to use \u001b
or \x1b
for the ESCAPE character.
But this code had potential for collisions with other sequences.
In C# 13 you can use \e
as a character for the ESCAPE character.
csharpvar text = "\x1b[1mThis text is bold\x1b[0m"; Console.WriteLine(text); var text = "\e[1mThis text is bold\e[0m"; Console.WriteLine(text);
7. Ref and Unsafe in Iterators and Async Methods
Before C# 13, the following methods couldn't declare local ref variables or have an unsafe context:
- async methods
- iterator methods - methods that use yield return
C# 13 removes these restrictions, and you can use Spans (that are ref structs) inside async methods:
csharppublic async Task ProcessTextAsync() { string text = await GetTextAsync(...); ReadOnlySpan<char> span = text; span = span.Slice(2, 10); Console.WriteLine(span.ToString()); }
However, those ref struct and unsafe variables can't be accessed across an await boundary. Neither can they be accessed across a yield return boundary.
8. Allows Ref Struct
Before C# 13, ref struct types couldn't be declared as the type argument for a generic type or method.
Now, generic type declarations can add an anti-constraint, allows ref struct. This anti-constraint declares that the type argument supplied for that type parameter can be a ref struct type. The compiler enforces ref safety rules on all instances of that type parameter.
This enables types such as System.Span<T>
and System.ReadOnlySpan<T>
to be used with generic algorithms, where applicable.
csharppublic class MyClass<T> where T : allows ref struct { // Use T as a ref struct: public void Do(scoped T p) { // The parameter p must follow ref safety rules } }
9. Ref Struct Interfaces
Starting from C# 13, ref struct can now implement interfaces.
However, to ensure ref safety rules, a ref struct type can't be converted to an interface type. That conversion is a boxing conversion, and could violate ref safety. From that rule, ref struct types can't declare methods that explicitly implement an interface method. Also, ref struct types must implement all methods declared in an interface, including those methods with a default implementation.
csharppublic interface ICoffee { } public ref struct Coffee : ICoffee { } Coffee coffee = new Coffee(); // This is not allowed ICoffee coffee2 = (ICoffee) coffee;
Hope you find this blog post useful. Happy coding!