C# 13 Features

C# 13 is the latest version of C# and it comes with a lot of new features. In this article, we will discuss some of the new features of C# 13.

params collections

With the C# 13, method parameter with params keyword isn't limited to be an array. You can now use any collection type that implements IEnumerable<T> interface.

Let's see how it can help us in our code.

public IEnumerable<int> GetOdds(params IEnumerable<int> numbers)
{
    foreach (var number in numbers)
    {
        if (number % 2 != 0)
        {
            Console.WriteLine(number);
        }
    }
}

New lock object

I'm sure you have used lock statement in your code to synchronize access to a shared resource. With C# 13, you can now use a new lock object that is more efficient than the traditional lock object.
The new Lock type provides better thread synchronization through its API. When Lock.EnterScope() method is called, it returns a struct named Scope that contains a Dispose method. The Dispose method is called when the Scope object goes out of scope, which releases the lock. C# using statement recognizes the Dispose method and calls it automatically like it does with other IDisposable objects.

It was something similar before:

private object _lock = new();

public void DoSomething()
{
    lock (_lock)
    {
        // Do something
    }
}

Now, you can use the new lock object like this:

System.Threading.Lock x = new System.Threading.Lock();
public void DoSomething()
{
    using (x.EnterScope())
    {
        // Do something
    }
}

New escape sequence

In C# 13, a new escape sequence \e has been introduced to represent the ESCAPE character, Unicode U+001B. Previously, you had to use \u001b or \x1b to represent this character. The new \e escape sequence simplifies this process and avoids potential issues with hexadecimal digits following \x1b.

You can check here for ANSI escape codes.

Implicit index access

The implicit "from the end" index operator, ^, is now allowed in an object initializer expression.

It was not possible before, but now you can do this:

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
    }
};

It's a great feature that makes the code more readable and maintainable. Still not a big deal, but it's nice to have it.

ref and unsafe in iterators and async methods

In C# 13, the restrictions on using ref and unsafe constructs in iterators and async methods have been relaxed. Previously, you couldn't declare local ref variables or use unsafe contexts in these methods. Now, you can declare ref local variables and use unsafe contexts in async methods and iterators, provided they are not accessed across await or yield boundaries

This change allows for more expressive and efficient code, especially when working with types like System.Span<T> and System.ReadOnlySpan<T>. The compiler ensures that these constructs are used safely, and it will notify you if any safety rules are violated.

You can read more about this feature on the Microsoft Learn page.

More partial members

In C# 13, the concept of partial members has been expanded to include partial properties and partial indexers. Previously, only methods could be defined as partial members. This means you can now split the definition of properties and indexers across multiple files, just like you could with methods.

For example, you can declare a partial property in one part of your class and implement it in another part. Here's a simple illustration:

public partial class MyClass
{
    // Declaring declaration
    public partial string MyProperty { get; set; }
}

public partial class MyClass
{
    // Implementing declaration
    private string _myProperty;
    public partial string MyProperty
    {
        get => _myProperty;
        set => _myProperty = value;
    }
}

This feature allows for better organization and modularization of your code, especially in large projects where different parts of a class might be implemented by different team members.

Overload resolution priority

What does "Overload resolution priority" section mean in this page?
In C# 13, the OverloadResolutionPriority attribute allows library authors to specify which method overload should be preferred by the compiler when multiple overloads are available. This attribute helps avoid ambiguity and ensures that the most appropriate overload is chosen, even if it might not be the most obvious choice based on traditional overload resolution rules.

This may be useful in scenarios where you have multiple overloads that are equally valid, but you want to prioritize one over the others. The attribute can be applied to a method or constructor to indicate its priority in the overload resolution process. It can prevent unexpected behavior and make your code more predictable and maintainable.

Let me show with an example:

public class Example
{
    // Existing method
    public void Display(string message = "Hello!")
    {
        Console.WriteLine("Message: " + message);
    }

    // New, more efficient method with higher priority
    [OverloadResolutionPriority(1)]
    public void Display(string message = "Hello!", int repeatCount = 3)
    {
        for (int i = 0; i < repeatCount; i++)
        {
            Console.WriteLine("Message: " + message);
        }
    }
}

class Program
{
    static void Main()
    {
        Example example = new Example();

        // Normally, you can't compile this code because of ambiguity:
        example.Display();
    }
}

Output:

Message: Hello!
Message: Hello!
Message: Hello!

The field keyword

n C# 13, the field keyword is introduced as a preview feature to simplify property accessors. This keyword allows you to reference the compiler-generated backing field directly within a property accessor, eliminating the need to declare an explicit backing field in your type declaration.

For example, instead of writing:

private int _value;
public int Value
{
    get => _value;
    set => _value = value;
}

You can now write:

public int Value
{
    get => field;
    set => field = value;
}

This makes your code cleaner and more concise. However, be cautious if you have a field named field in your class, as it could cause confusion. You can disambiguate by using @field or this.field.

Make sure you're using the latest LangVersion in your .csproj project file to enable this feature.

<LangVersion>preview</LangVersion>