New Features in C# 13: A Friendly Beginner's Guide
Learn the new features in C# 13 with simple words, real-life examples, diagrams, and code you can read in minutes. Great for beginners.
C# keeps growing every year. Each new version adds small tools that make your code shorter, safer, and easier to read. C# 13 came out with .NET 9, and it has some really handy gifts for us.
In this guide we will walk through the main new features one by one. We will use simple words and small examples. By the end, you will know what changed and why it matters.
A real-life way to think about it
Imagine your mother is cooking in the kitchen. She has a steel tiffin box. Earlier, this box could only hold rice. If she wanted to carry dal, she needed a different container. That was a rule, and it was a bit annoying.
Now imagine someone improves the tiffin box so it can hold rice, dal, sabzi, or even just one roti. Same box, but it accepts many kinds of food. Your mother does not change how she packs lunch. The box just became more flexible.
C# 13 is a lot like that improved tiffin box. Many features take something that already worked, and make it accept more cases or work more safely. You do not throw away what you know. You just get more room to move.
Let us see how the language grew this year.
Feature 1: params collections
You may have used the params keyword before. It lets a method accept any number of arguments. Here is the old style.
// Old way: params only worked with an array
void PrintNames(params string[] names)
{
foreach (var name in names)
{
Console.WriteLine(name);
}
}
PrintNames("Asha", "Ravi", "Meena"); // works fineThe catch is that params only worked with arrays. Arrays are fine, but they always create a new block of memory. Sometimes you want something lighter, like a Span<T> or a ReadOnlySpan<T>, which can avoid extra memory work.
In C# 13, params now works with many collection types. You can use spans, lists, and any type that the compiler knows how to build.
// New in C# 13: params works with Span and other collections
void PrintNames(params ReadOnlySpan<string> names)
{
foreach (var name in names)
{
Console.WriteLine(name);
}
}
PrintNames("Asha", "Ravi", "Meena"); // same call, lighter under the hoodWhy does this help? When you use a ReadOnlySpan<string> here, the compiler can often skip creating a full array on the heap. That means less memory pressure and faster code in hot paths. The nice part is that the person calling your method does not see any difference. They still write the same simple call.
Here is a quick comparison.
| Point | Old params (array) | New params collections |
|---|---|---|
| Allowed types | Only arrays | Arrays, spans, lists, and more |
| Memory cost | Always allocates an array | Can avoid heap allocation |
| Call site code | Same simple call | Same simple call |
| Best for | General use | Performance-sensitive code |
Feature 2: the new lock object
When many tasks run at the same time, they can fight over the same data. To stop the fight, we use a lock. It is like a single bathroom key in a hostel. Only one person can hold the key at a time, so only one person enters.
For years, C# used the lock keyword on a plain object. Behind the scenes it used a class called Monitor. It worked, but it was not made for this job from the start.
.NET 9 adds a brand new type made just for locking: System.Threading.Lock. The C# 13 compiler is smart. When you lock on a Lock object, it quietly uses the new, faster API.
using System.Threading;
public class BankAccount
{
// New dedicated lock type from .NET 9
private readonly Lock _balanceLock = new();
private decimal _balance;
public void Deposit(decimal amount)
{
lock (_balanceLock) // compiler uses the new Lock API
{
_balance += amount;
}
}
}Notice how little changed. You used to write private readonly object _balanceLock = new();. Now you write Lock instead of object. The lock block looks the same. That is the whole point. You get a cleaner, faster lock by changing one word.
There is also a method called EnterScope() for people who like the using pattern.
public void Deposit(decimal amount)
{
using (_balanceLock.EnterScope())
{
_balance += amount;
}
}The diagram below shows how one task waits while another holds the key.
How the new lock works
Steps
Enter
Take the lock
Work
Change shared data safely
Release
Free the lock for others
Feature 3: partial properties and indexers
You may already know partial classes. They let you split one class across two files. This is very common when a tool generates part of your code (like a source generator), and you write the rest by hand.
Before, you could split methods this way. In C# 13, you can also split properties and indexers.
The idea has two parts: a declaring part that says "this property exists," and an implementing part that says "here is how it works."
// File 1: generated by a tool
public partial class Person
{
public partial string FullName { get; set; }
}
// File 2: written by you
public partial class Person
{
private string _name = "";
public partial string FullName
{
get => _name;
set => _name = value.Trim();
}
}One rule to remember: the implementing part cannot be a simple auto-property like { get; set; }. It must have a real body, as shown above. This keeps things clear about which part is the "real" code.
This feature mostly helps library authors and code generators. As a beginner, you may not write this every day. But when you see it in a generated file, you will now know what it means.
Feature 4: the new escape sequence \e
This one is small but fun. In text, some characters are special and need an "escape." For example, \n means a new line and \t means a tab.
C# 13 adds \e. It stands for the ESCAPE character (Unicode U+001B). This character is used to send special commands to a terminal, like making text bold or colored using ANSI codes.
// \e is the new escape character in C# 13
Console.WriteLine("\e[1mThis text is bold\e[0m");
Console.WriteLine("\e[31mThis text is red\e[0m");Before C# 13, people wrote or \x1b to get the same character. Those worked, but \x1b could sometimes cause confusing bugs because it greedily reads extra characters. The new \e is short, clear, and safe.
Feature 5: ref struct can do more
This part is a little more advanced, so do not worry if it feels new. A ref struct is a special kind of struct that must live on the stack. Span<T> is the most famous example. It is fast, but it had many rules about where you could use it.
C# 13 relaxes several of these rules. Here is a friendly table of what changed.
| Change in C# 13 | What it means for you |
|---|---|
ref struct can implement interfaces | Your span-like types can promise behavior, like IDisposable |
allows ref struct in generics | A generic method can accept a ref struct type argument |
ref struct in async and iterators | You can use spans more freely (not across an await) |
ref locals in async methods | More room to write fast code |
A small taste of the generics part looks like this.
// "allows ref struct" lets T be a ref struct like Span<T>
public void Process<T>(T value)
where T : allows ref struct
{
// T can now be Span<int>, ReadOnlySpan<char>, etc.
Console.WriteLine("Processing a value");
}You will not use these every day as a beginner. But it is good to know the language is removing old limits so advanced, fast code becomes easier to write.
Feature 6: other small touches
C# 13 also brings a few smaller improvements that are nice to know:
- Implicit index access in object initializers. You can use the
^(from-the-end) index when setting up array items inside an object initializer. Lockworks withusing. As we saw,EnterScope()plays well with theusingstatement.- Better overload resolution in some
paramscases, so the compiler picks the best method more often.
None of these will change your daily code much, but together they make the language feel smoother.
Putting it all together
Let us look at one tiny example that uses two of the new features at once: the new Lock type and params collections.
using System.Threading;
public class Logger
{
private readonly Lock _gate = new();
// params collection: accepts any number of messages cheaply
public void Log(params ReadOnlySpan<string> messages)
{
lock (_gate)
{
foreach (var message in messages)
{
Console.WriteLine($"[LOG] {message}");
}
}
}
}
var logger = new Logger();
logger.Log("App started", "User signed in", "Data loaded");This small class is safe across threads (thanks to Lock) and light on memory (thanks to the span-based params). And it is still very easy to read.
Your path to learning C# 13
Steps
Update SDK
Install .NET 9
Try one feature
Maybe params or Lock
Read the docs
Microsoft Learn is your friend
Build something
Practice makes it stick
How to start using C# 13
Getting C# 13 is easy. You need .NET 9 installed. When your project targets net9.0, the compiler uses C# 13 by default. You usually do not need to set anything by hand.
If you ever need to be sure, you can add this line to your project file:
// In your .csproj file
// <LangVersion>13</LangVersion>After that, just write code as usual. The new features are there waiting for you.
A quick note for the curious: the .NET world keeps moving. .NET 10 is now the long-term support release, and C# 14 has already shipped with even more goodies. C# 15 is bringing union types in the .NET 11 preview. But everything you learn in C# 13 still works in the newer versions. Learning C# 13 well is a strong base for the future.
Quick recap
- params collections let
paramsaccept spans and other collections, not just arrays. This can save memory. - The new
System.Threading.Locktype gives you faster, cleaner locking. Just changeobjecttoLock. - Partial properties and indexers let you split a property across files, which helps code generators.
- The
\eescape sequence is a short, safe way to write the ESCAPE character for terminal colors and styles. ref structtypes gained new powers: they can implement interfaces, be used in generics withallows ref struct, and appear in async and iterator methods.- A few small touches like index access in object initializers make the language smoother.
- To use it all, target .NET 9. The compiler picks C# 13 for you.
You do not have to memorize everything. Pick one feature, try it in a small program, and let it sink in. That is how every good developer learns.
References and further reading
- What's new in C# 13 (Microsoft Learn)
- System.Threading.Lock class (Microsoft Learn)
- The lock statement (Microsoft Learn)
- C# 13: Explore the latest features (.NET Blog)
- The best new features in C# 13 (InfoWorld)
Related Posts
The New LINQ Methods from .NET 6 to .NET 9: A Friendly Guide
Learn the new LINQ methods added in .NET 6, 7, 8 and 9 with simple words, real-life examples, diagrams and clean C# code. Great for beginners.
How to Write Elegant Code with C# Pattern Matching
Learn C# pattern matching the easy way. Use is, switch expressions, property and list patterns to write clean, readable, and elegant .NET code.
How to Apply Functional Programming in C#: A Beginner's Guide
Learn functional programming in C# the simple way: pure functions, immutability, records, LINQ, pattern matching, and composition with friendly examples.
Mastering Exception Handling in C#: A Comprehensive Guide
Learn C# exception handling the friendly way: try, catch, finally, custom exceptions, filters, throw vs throw ex, and real best practices for .NET 10.
Getting Started with C# Records: A Beginner's Friendly Guide
Learn C# records the easy way: value equality, with expressions, positional syntax, and record struct, explained with simple real-life examples.
Why I Switched to Primary Constructors for DI in C#
A friendly guide on why primary constructors made my C# dependency injection cleaner, with simple examples, diagrams, tables, and honest trade-offs.