Skip to main content

C# 13 New Features: Complete Guide for Developers (2026)

Learn all C# 13 new features with runnable code examples — params collections, the Lock type, partial properties and more. Master C# 13 today.

C# 13 shipped alongside .NET 9, and by 2026 it has become the version most production teams are writing every day. If you're searching for C# 13 new features, this guide walks through every major addition — params collections, the new System.Threading.Lock type, partial properties, implicit index access, and more — with runnable code examples and, just as importantly, an explanation of why each feature exists and when you should reach for it.

Whether you're a beginner wondering how to use the new syntax, an intermediate developer looking for best practices, or a senior engineer evaluating advanced ref struct capabilities, there's something here for you. Let's dig in.

What's New in C# 13? A Quick Overview

Here's the full list of C# 13 new features at a glance:

  • params collectionsparams now works with Span<T>, IEnumerable<T>, lists, and any collection type, not just arrays
  • New System.Threading.Lock type — a dedicated, faster lock object that replaces locking on plain object
  • Partial properties and indexers — huge for source generators
  • Implicit index access (^) in object initializers
  • Overload resolution priority — library authors can steer the compiler toward better overloads
  • ref and unsafe in iterators and async methods
  • ref struct types can implement interfaces and be used as generic type arguments via allows ref struct
  • The \e escape sequence for the ESC character
  • Better natural type for method groups

You need the .NET 9 SDK (or newer) and <LangVersion>13</LangVersion> — or simply <TargetFramework>net9.0</TargetFramework>, which enables C# 13 by default. Now let's look at each feature in depth.

C# 13 New Features Explained with Code Examples

1. params Collections — No More Array Allocations

For two decades, params only worked with arrays. Every call to a params method silently allocated an array on the heap — even for two or three arguments. C# 13 fixes this by allowing params on any collection type, most importantly ReadOnlySpan<T>:

// C# 12 and earlier — heap allocation on every call
public static int SumOld(params int[] numbers)
{
    int total = 0;
    foreach (var n in numbers) total += n;
    return total;
}

// C# 13 — the compiler can place arguments on the stack. Zero heap allocation.
public static int Sum(params ReadOnlySpan<int> numbers)
{
    int total = 0;
    foreach (var n in numbers) total += n;
    return total;
}

// Call site looks identical
int result = Sum(1, 2, 3, 4, 5); // no array allocated

It also works with interfaces and concrete collections:

public static void PrintAll(params IEnumerable<string> items)
{
    foreach (var item in items)
        Console.WriteLine(item);
}

public static List<T> MakeList<T>(params List<T> items) => items;

PrintAll("apple", "banana", "cherry");

Why it matters: in hot paths — logging, string formatting, math helpers — the hidden array allocations from old-style params add measurable GC pressure. With params ReadOnlySpan<T>, the compiler stack-allocates the arguments. When both a span overload and an array overload exist, the compiler prefers the span, so libraries like String.Concat got faster in .NET 9 without anyone changing their calling code.

Best practice: prefer params ReadOnlySpan<T> for new APIs unless you need to store the collection, in which case take params IEnumerable<T> or an array.

2. The New System.Threading.Lock Type

This is one of the most searched C# 13 new features, and for good reason. Since C# 1.0, developers have locked on plain object instances. C# 13 introduces a dedicated Lock type that is both faster and safer:

public class OrderProcessor
{
    private readonly Lock _lock = new();
    private int _processedCount;

    public void Process()
    {
        lock (_lock) // compiler emits Lock.EnterScope(), not Monitor.Enter()
        {
            _processedCount++;
            // critical section
        }
    }

    public void ProcessManually()
    {
        // Equivalent explicit form — useful when you need finer control
        using (_lock.EnterScope())
        {
            _processedCount++;
        }
    }
}

Why it matters: when the compiler sees lock on a Lock instance, it emits calls to Lock.EnterScope() instead of Monitor.Enter/Monitor.Exit. The new type avoids the object-header/sync-block machinery of Monitor, which makes it measurably faster under contention. It also communicates intent: a field of type Lock is unambiguously a lock, not a random object someone might reuse.

Common pitfall: don't box a Lock by assigning it to an object variable and locking on that — you'd silently fall back to Monitor and end up with two different synchronization mechanisms guarding the same state. The compiler warns about this (CS9216); treat that warning as an error.

3. Partial Properties and Indexers

C# 3 gave us partial methods; C# 13 extends the idea to properties and indexers. The killer use case is source generators:

// Your file
public partial class ViewModel
{
    [ObservableProperty]
    public partial string UserName { get; set; }
}

// Generated file (e.g. by the MVVM Toolkit)
public partial class ViewModel
{
    private string _userName = string.Empty;

    public partial string UserName
    {
        get => _userName;
        set
        {
            if (_userName != value)
            {
                _userName = value;
                OnPropertyChanged(nameof(UserName));
            }
        }
    }
}

Why it matters: before C# 13, source generators had to invent naming conventions — you'd write a field named _userName and the generator produced a property named UserName. That broke navigation, refactoring, and discoverability. With partial properties, you declare the actual property and the generator fills in the implementation. Regex source generators, MVVM frameworks, and logging generators all adopted this pattern quickly.

4. Implicit Index Access in Object Initializers

The "from the end" index operator ^ now works inside object initializers:

public class Countdown
{
    public int[] Values { get; set; } = new int[10];
}

var countdown = new Countdown
{
    Values =
    {
        [^1] = 0,   // last element
        [^2] = 1,
        [^3] = 2
    }
};
// countdown.Values = [0, 0, 0, 0, 0, 0, 0, 2, 1, 0]

A small feature, but it removes an inconsistency: ^ worked everywhere except initializers. Now the language is uniform.

5. Overload Resolution Priority (Advanced)

Library authors can now tell the compiler which overload to prefer when several are applicable, using [OverloadResolutionPriority]:

using System.Runtime.CompilerServices;

public static class Logger
{
    [OverloadResolutionPriority(1)]
    public static void Log(ReadOnlySpan<char> message)
        => Console.Out.Write(message); // fast path, preferred

    public static void Log(string message)
        => Console.Out.Write(message); // kept for binary compatibility
}

Why it matters: this lets libraries add faster overloads without breaking existing compiled code or causing ambiguity errors. The .NET base class library uses it extensively. Best practice for application developers: you almost never need this in app code — it's a library-author tool. Overusing it makes overload resolution harder to reason about.

6. ref and unsafe in Iterators and Async Methods

Before C# 13, you couldn't declare a ref local or use a ref struct like Span<T> anywhere inside an async method or iterator. C# 13 relaxes this: you can use them as long as they don't live across an await or yield return boundary:

public async Task<int> CountDigitsAsync(string input)
{
    // Legal in C# 13: Span usage before the await, not across it
    ReadOnlySpan<char> span = input.AsSpan();
    int digits = 0;
    foreach (var c in span)
        if (char.IsDigit(c)) digits++;

    await Task.Delay(10); // span is not used after this point

    return digits;
}

Why it matters: previously you had to extract span-based logic into a separate synchronous helper method purely to satisfy the compiler. Now high-performance parsing code and async I/O can coexist in one method, with the compiler still guaranteeing that stack-only types never survive a suspension point.

7. ref struct Interfaces and allows ref struct (Advanced C#)

C# 13 lets ref struct types implement interfaces, and lets generic parameters opt in to accepting them:

public interface IWriter
{
    void Write(ReadOnlySpan<char> text);
}

// A ref struct can now implement an interface
public ref struct BufferWriter : IWriter
{
    private Span<char> _buffer;
    private int _position;

    public BufferWriter(Span<char> buffer) => _buffer = buffer;

    public void Write(ReadOnlySpan<char> text)
    {
        text.CopyTo(_buffer[_position..]);
        _position += text.Length;
    }
}

// Generic code opts in with 'allows ref struct'
public static void WriteGreeting<T>(T writer) where T : IWriter, allows ref struct
{
    writer.Write("Hello, C# 13!");
}

The critical pitfall: you still cannot box a ref struct. Casting BufferWriter to IWriter directly is a compile error — the interface can only be used as a generic constraint. This preserves the stack-only safety guarantee while finally letting Span-based types participate in generic abstractions. This feature is why .NET 9+ APIs like TensorPrimitives and the LINQ-like span extensions could be built at all.

8. Smaller Quality-of-Life Improvements

  • \e escape sequence: write "\e[1;31mRed text\e[0m" for ANSI terminal colors instead of the awkward "\u001b" or "\x1b" (the latter of which has a nasty parsing gotcha when followed by hex digits).
  • Method group natural type: the compiler now prunes inapplicable candidate methods scope-by-scope when inferring a delegate type for a method group, making more code with var handler = SomeMethod; just work — and compile faster.

C# 13 Best Practices and Common Pitfalls

  • Migrate hot-path params APIs to ReadOnlySpan<T>. It's binary-compatible to add the overload alongside the array version, and callers get the faster path on recompile.
  • Replace private readonly object _lock = new(); with private readonly Lock _lock = new(); in new code. Don't mix the two mechanisms on the same state.
  • Don't sprinkle [OverloadResolutionPriority] in application code. It exists for library evolution scenarios.
  • Watch ref struct boxing errors. If you see CS0029 converting a ref struct to an interface, you need a generic method with allows ref struct, not a cast.
  • Remember the async boundary rule. Span<T> in async methods is fine between awaits, never across them — the compiler enforces this, but designing with it in mind saves refactoring.
  • Note on the field keyword: it was preview-only in C# 13 and became a full feature in C# 14 (.NET 10). If you're on .NET 9 with LangVersion 13, don't rely on it in production code.

Conclusion: Why C# 13 New Features Matter in 2026

The theme running through all the C# 13 new features is performance without ceremony. params collections remove hidden allocations, the Lock type speeds up the most common synchronization pattern in the language, and the ref struct improvements let high-performance span-based code finally participate in generics, iterators, and async methods. Meanwhile, partial properties quietly transformed the entire source-generator ecosystem.

Key takeaways:

  • Use params ReadOnlySpan<T> for allocation-free variadic APIs.
  • Adopt System.Threading.Lock for new synchronization code — it's faster and clearer.
  • Partial properties make source generators first-class; expect your MVVM and Regex code to use them.
  • allows ref struct and ref struct interfaces unlock generic abstractions over Span<T>.
  • ref and unsafe now work in async methods and iterators, as long as they don't cross suspension points.

If you're targeting .NET 9 or later, every one of these features is available today. Pick one — the Lock type is the easiest win — and start using it in your next pull request. And when you're ready to go further, check out our other C# tutorials on csharp-coder.com covering C# 14, .NET 10, and modern high-performance patterns.

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

Comments

Popular posts from this blog

.NET MAUI Tutorial 2026: Build Cross-Platform Apps in C#

Learn .NET MAUI in 2026 to build iOS, Android, Windows & Mac apps from one C# codebase. Start this cross-platform tutorial with code examples today. .NET MAUI (Multi-platform App UI) is Microsoft's framework for building native iOS, Android, Windows, and macOS apps from a single C# codebase . If you've ever wanted to ship a mobile app without learning Swift, Kotlin, and Win32 separately, this .NET MAUI tutorial for 2026 is your starting point. In this guide you'll learn what .NET MAUI is, why it matters for cross-platform app development in C#, and how to build your first working app — with runnable code examples and the best practices senior engineers actually use in production. What Is .NET MAUI and Why Use It in 2026? .NET MAUI is the evolution of Xamarin.Forms, fully integrated into the modern .NET runtime. With one project and one language — C# — you target four platforms. The framework compiles to native UI controls on each device, so a button on iOS...

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...