The next version of C# (C# 13) brings a great deal to the table with increased flexibility, performance, and usability. From improved params
collections and efficient locking mechanisms to enhancements in pattern matching and interpolated strings, C# 13 is packed with tools to improve productivity. Let’s cut to these exciting updates on how they can revolutionize your coding experience.
How to Try C# 13 Today?
So before we go through the new features in C# 13 you are going to have to get your environment set up. Install the latest Preview release of C# 13 in the latest preview of .NET 9 (today that would be Preview 6 as I write this post) and the latest Visual Studio 2022-17.11 Preview release. To take advantage of the preview features, the preview language version is set in the project file as:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<LangVersion>preview</LangVersion>
<!--other settings-->
</PropertyGroup>
</Project>
Advanced params Collections
C# 13 extends the params keyword one step further by making it work for any type that can be constructed using a collection expression, making method calls more flexible.
public void WriteUsers(params IEnumerable<string> users)
=> Console.WriteLine(String.Join(", ", users));
// Both would be output: User 1, User 2
WriteNames("User 1", "User 2");
WriteNames(new string[] {"User 1", "User 2"});
var users = new List<User>
{
new User("Tyler", "Durden"),
new User("Robert", "Paulson"),
new User("Marla", "Singer"),
new User("Bob", "Big")
};
// All of the following output: Tyler, Robert, Marla, Bob
WriteNames("Tyler", "Robert", "Marla", "Bob");
WriteNames(users.Select(user => user.FirstName));
WriteNames(from u in users select u.FirstName);
More Efficient Locking using System.Threading.Lock
The new System.Threading.Lock
type in .NET 9 makes locking more efficient compared to using an arbitrary object and is fully integrated with the lock statement in C# 13.
public class ExampleClass
{
private System.Threading.Lock resourceLock = new System.Threading.Lock();
public void ExecuteTask()
{
lock (resourceLock)
{
// Your code
}
}
}
Index from the End in Initializers
The ^
index operator now works in initializers, allowing for more intuitive and concise code.
class Cart
{
public string[] Items { get; set; } = new string[10];
}
var cart = new Cart
{
Items =
{
[1] = "Kiwi",
[^1] = "Lemon" // Works starting in C# 13
}
// cart.Items[1] is Kiwi
// cart.Items[9] is Lemon, since it is the last element
};
New Escape Sequence
New escape sequence \e
makes it easier to work with terminals and VT100/ANSI escape codes.
// Before C# 13
Console.WriteLine("\u001b[31mmHello, World! This text is red.\u001b[0m");
// With C# 13
Console.WriteLine("\e[31mHello, World! This text is red.\e[0m");
Partial Properties
C# 13 introduces partial properties to support source generators, providing a more natural API.
[GeneratedRegex("hello|world")]
private static partial Regex HelloOrWorldProperty { get; };
string text = "hello, everyone!";
if (HelloOrWorldProperty.IsMatch(text))
{
Console.WriteLine("The text contains 'hello' or 'world'.");
}
else
{
Console.WriteLine("The text does not contain 'hello' or 'world'.");
}
Method Group Natural Type Improvements
Refinements in the rules for determining the natural type of method groups reduce compiler errors and make the language more robust.
TaskItem GetTask() => new(Id: 1, Description: "Complete the project");
Func<TaskItem> taskFunc = GetTask;
TaskItem task = taskFunc();
Console.WriteLine($"Task ID: {task.Id}, Description: {task.Description}");
Support for ref struct in Generics
C# 13 allows you to specify that a type parameter can be a ref struct
, expanding the types that can be used in generic type parameters.
T GenericIdentity<T>(T parameter)
where T : allows ref struct
=> parameter;
Span<int> spanExample = GenericIdentity(new Span<int>(new int[5]));
ref and unsafe in async Methods and Iterators
Before C# 13, both iterator methods—methods that use yield return
—and async
methods couldn’t declare local ref
variables, nor could they have an unsafe context. In C# 13, we can have async methods declare ref
local variables, or local variables of a ref struct type. These variables cannot be captured across an await boundary or a yield return boundary. Similarly, in C# 13 unsafe contexts are allowed within iterator methods, but all yield return and await statements are required to be in safe contexts. These eased-up constraints will allow you to use ref local variables and ref struct types in more places.
I hope you enjoyed our exploration of the new features in C# 13. I tried to provide a comprehensive look at some of the most exciting and useful additions to the language. Have you had a chance to try out any of these new features in your projects? What are your thoughts?
I’d love to hear about your experiences and get your feedback. Please leave your comments below. Your insights will help me improve my content and make it even more valuable to you. Thank you for reading the amarozka.dev blog and sharing your thoughts!