What's New in C# 13

As I've done every year since starting this blog, this is the 5th edition, let's take a look at the new features brought by C# in its latest release, C# 13.

 

Before we begin, it's worth noting that C# 13, or rather .NET 9 (which we'll analyze in a future post), is a short-term support version. This means that after 18 months it will no longer receive support, so I imagine many of you might not use it in your companies.

 

We won't dive into the performance improvements in .NET 9 here; for that, Microsoft has a very in-depth blog post (beware, it might crash your browser for being too lengthy!). Here's the link: Blog post on the performance improvements in net 9.

If you want to try out the new features in C# 13, you need to install .NET 9 on your machine, as they're tightly coupled.

 

 

TL;DR: Syntax sugar and performance improvements.

 

1 - New Breaking Changes Policy in C#

 

This isn't a feature per se for the language, but a change in how the language evolves, and in my opinion, it's the best improvement in this release.

 

We've all seen over recent years that when one feature replaced another in C#, there ended up being two different ways to do the same thing. While that might sound like a good idea, it really isn't, especially for juniors, as it can cause confusion.

 

This policy existed to avoid breaking changes, but now, the Microsoft team has decided that, on occasion, there will be breaking changes, and that's just how it is. I personally think it's a good move, though I'd still mark anything deprecated as obsolete in one LTS release, and then remove it in the next LTS or in two versions' time.

 

2 - Thread Synchronization with Lock

The lock keyword has existed in C# since version 1, and in over 20 years, its behavior hasn't changed. In C# 13, a new version has been added under System.Threading.Lock, which doesn't replace the current one but improves what we have.

 

Both approaches work similarly, or at least aim for the same goal: preventing multiple threads from modifying the same object simultaneously.

 

The major advantage now is that it allows the pattern Dispose to be implemented via the EnterScope() method, and it’s compatible with the using keyword. This means, if an exception happens, the resource will be safely released, improving reliability.

 

Here’s the new syntax:

System.Threading.Lock myLock = new System.Threading.Lock();

lock (myLock)
{

}

// Internally
using (myLock.EnterScope())
{
    // Code
}

In summary, the language syntax you'll use remains the same. Its implementation is more powerful because it improves performance and avoids risks we previously faced within a lock.

Here's an explanation link for the Dispose pattern.

 

3 - Changes to the params Keyword

 

The params keyword is one of those you never realize is there until you need it.

Basically, it lets a method accept multiple input parameters of the same type, bundling them into an array behind the scenes.

 

Maybe I'm wrong, but in C#'s early days, the structure of a console application looked like this:

class Program
{
    static void Main(params string[] args)
    {
        Console.WriteLine("Hello, World!");
    }
}

This means you could pass as many string variables as you wanted, and they'd be converted into an array. Although this example is for the entry method, any other method could use the params keyword, which could then be used as simply as this:

Main("a", "b", "c")

The change in C# 13 is that the params keyword now works not just with arrays, but also with other collections like List, IEnumerable, or Span. This makes the code more flexible because you're no longer limited to arrays, you can use more modern and efficient collections too.

 

4 - Implicit Index Operator for Object Initialization

 

When pattern matching arrived a couple of versions ago, it came along with the ^ operator, which lets you access array elements from the end.

 

If you have an int[] array and use int[^1], you access the last item of the array; int[^2] gets the second-to-last, and so on.

With this new change, you can not only access array elements, but also assign values this way when initializing an object.

 

To see this in action, in the usual case you'd assign values in order:

int[] example = new int[]
{
    1, 2, 3
};

This would output [1,2,3]

 

With the new functionality:

ExampleInverseAssignation exampleInverseAssignation = new ExampleInverseAssignation
{
	Values =
	{
		[^1] = 1,
		[^2] = 2,
		[^3] = 3
	}
};

class ExampleInverseAssignation
{
	public int[] Values { get; set; } = new int[3];
}

Here, you're starting assignments with the last element, so the array ends up as [3,2,1].

 

Does this have any real use? Well, sometimes you do need to iterate arrays in reverse, it doesn't come up too often in my experience, but it lets you skip some calculations, which always helps performance, especially with list-reversal algorithms, data processing, or just for code readability.

 

5 - Improved Natural Type Resolution for Method Groups

 

I'm not going into deep detail because this section deals purely with the compiler. In short, the way code picks which method to execute is now more efficient and therefore faster.

 

For context, there are situations where the compiler has a list of possible methods to run, which it must evaluate one by one to determine the right one. This was inefficient, especially when using generics with complex constraints, as it required analyzing the entire list.

 

C# 13 introduces a more efficient process that first narrows the number of methods to those "in-scope" (i.e., on the instance itself or as extension methods), then removes those that can't be executed, like generics with a different number of parameters or ones that don’t meet constraints.

 

By removing these methods upfront, the compiler's workload is reduced, which means greater efficiency.

 

6 - New Escape Sequence

 

A minor change, but perhaps relevant to some (not for me personally), a new escape sequence, \e, has been introduced to represent the ESCAPE character.

 

Previously, you had to use the unicode character \u001b or \x1b. That could be problematic: if a valid hexadecimal character followed the 1b in either option, it would be interpreted as part of the escape sequence, causing unexpected results. The new escape is more robust.

 

7 - Partial Members

 

I assume everyone knows about partial classes, well, you can now declare partial properties and indexers, following the same logic.

 

Now you can declare partial members, and those declarations must be implemented. This is useful when you want others to extend your functionality.

For example, you could declare a partial event and allow others to add code to that event's execution.

 

Personally, I see a strong use case for this in source generators.

 

8 - ref and unsafe in Iterators and async Methods

 

Before C# 13, methods that used yield return and async could not declare local variables as ref or use unsafe. In C# 13, this is now possible, though with some limitations.

 

This change means you can now use ref struct types like Span<T> or ReadOnlySpan<T>, which are efficient for memory operations.

 

About unsafe: if you don’t know, this keyword allows manual memory access and pointer usage.

public unsafe IEnumerable<int> GetData()
{
    int* data = stackalloc int[10];
    for (int i = 0; i < 10; i++)
    {
        data[i] = i * i;
        yield return data[i]; 
    }
}

This now lets you do more advanced operations, like the pointer example above, without compromising code safety when yielding iterator values.

 

9 - Changes Not Ready for C# 13

 

When these updates were first announced, there were plans for a few more changes. For various reasons, they didn’t make this release, but they'll likely come in the next one.

 

9.1 - Auto-properties with Custom Logic

 

If you’ve been in the game for a while, you’ll remember we used to have to manually define getters and setters, until we switched to the get set shorthand.

public class Vehicle
{
    public string Make { get; set; }
}

This was a huge improvement, cutting lots of unnecessary code. Well, most companies switched, although I've seen code as late as 2020 still using the old getter/setter pattern.

 

Either way, behind the scenes, the compiler generates a backing field, like this:

class Vehicle
{
    private string _make;  // generated (compiler)
    public string Make    // property (user)
    {
        get => _make;
        set => _make = value;
    }
}

All of this is transparent to the user, unless you need customization in the getter or setter.

So, when you need extra config, you’d normally write the backing field out manually, to add your logic. For example, suppose you only want cars where the brand is Audi; for all others, you want to hide the value. One way to do this:

class Vehicle
{
    private string _make;  // generated (compiler)
    public string Make    // property (user)
    {
        get => _make;
        set
        {
            if (value == "audi")
            {
                _make = value;
            }
            else
            {
                _make = "";
            }
        }
    }
}

Note: you can use the ternary operator to shorten the if statement.

 

With the new C# 13 approach, you don't need to manually create the field, the compiler recognizes it exists at development time, and you can access it, reducing the lines of code needed.

class Vehicle
{
    public string Make    // property (user)
    {
        get => field;
        set => field = value == "audi" ? value : "";
    }
}

 

Bear in mind, if you already have a variable named field, this will cause a breaking change.

It looks like the plan is to convert the field identifier into a real language keyword, here's the proposal on GitHub.

You can subscribe to this issue to stay updated.

 

9.2 - Extension Methods for Properties, Indexers, and Static Members

 

extension methods everywhere

 

Since C# 3, we've had extension methods, and personally, I use them all the time because I love them. The plan was to allow them for properties and indexers too.

 

But since this all got postponed to C# 14, we'll see it next year. If you're curious, here's the announcement from Microsoft.

 

9.3 - Union types / Discriminated unions in C#

 

Although not officially announced for C# 14, there's a proposal to use union types, which we'll probably talk about a lot in 2025.

UPDATE: It turns out that this is more complex than it seems, they may delay it until C# 16 or 17 (clip here).

 

The same proposal suggests including the Result<T> type as a built-in language type. You know I'm a big fan of this style of programming, and I've even written a post and a library on it.

 

Here's the link to my blog.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café