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.
- Launch the Visual Studio IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window, specify the name and location for the new project.
- Click Next.
- In the “Additional information” window shown next, choose “.NET 9.0 (Preview)” as the framework version you would like to use.
- 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
and System.ReadOnlySpan
, as well as types that implement the System.Collections.Generic.IEnumerable
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
=> Console.WriteLine(String.Join(", ", authors));
Moreover, if you call a method that accepts an IEnumerable
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
.
void Display(params IEnumerable
=> Console.WriteLine(String.Join(", ", authors));
var authors = new List
{
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
public void AddData(params IReadOnlySpan
What are the benefits of these overloads? By adding an IEnumerable
overload, support for LINQ is enabled. And by adding a ReadOnlySpan
Finally, note that per the official blog, several methods pertaining to the .NET runtime have been updated to accept params Span
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
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
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. The code example below shows how you can define a struct and a static method that uses allows ref struct
.
static void TestMethod(T obj) where T : allows ref struct
{
//Write your code here
}
ref struct TestStruct
{
}
You can now use the struct and static method as shown in the code snippet below.
TestStruct testStruct = new();
TestMethod(testStruct);
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.
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.
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.