Back to: C# New Features Tutorials
C# 13 New Features with Examples
In this article, I will give an overview of C# 13 New Features with Examples. C# 13 was introduced alongside .NET 9. It focuses on performance, developer productivity, code clarity, and greater extensibility, especially for APIs, libraries, and high-performance systems.
C# 13 is about writing less code to do more while improving runtime performance and code extensibility. Whether you’re building desktop, web, cloud, or mobile apps, these features help you write smarter, faster, and future-ready applications. The following are the main features in C# 13:
- Enhanced Collection Support with params:
- Advanced Lock for Async-Friendly Synchronization
- Partial Properties & Indexers
- ref / unsafe in async Methods and Iterators
- Interface Implementation for Ref Struct Types
- The field Contextual Keyword
- Extension Members Beyond Methods
- Overload Resolution Priority Attribute
- Primary Constructors for Records
- Default Interface Implementations for Static Members
System Requirements to Test C# 13 Features:
To test the new C# 13 features, you must have the appropriate versions of the .NET SDK and Visual Studio. Here is what you will need:
.NET SDK
For C# 13 features, you will need the .NET 9 SDK, as C# 13 is introduced alongside .NET 9.
- Required Version: .NET 9 SDK or later (C# 13 is a part of this release)
- Download Link: https://dotnet.microsoft.com/en-us/download/dotnet/9.0
Ensure that you have the correct version installed by checking the SDK version with the following command in your terminal or command prompt: dotnet –version. This should output a version starting with 9.x.x.
Visual Studio for Windows
To work with C# 13, Visual Studio 2022 (version 17.10 or later) is required. This version fully supports .NET 9 and the new C# 13 features, including improved IntelliSense and tooling for working with new language features.
- Required Version: Visual Studio 2022 version 17.12 or later.
- Download Link: https://visualstudio.microsoft.com/downloads/
Make sure you select the following workloads during installation:
- .NET Desktop Development
- ASP.NET and web development (if you’re working on web applications)
- Mobile development with .NET (if you’re working on mobile apps)
Visual Studio Code for Mac and Linux:
If you prefer a lightweight editor, or if you are using macOS or Linux, then need to use Visual Studio Code with the C# extension—just point it at your installed .NET 9 SDK and set <LangVersion>preview</LangVersion> (or 13.0) in your project file to enable the new features.
Operating System Requirements
Both .NET 9 and Visual Studio 2022 should work on the following operating systems:
- Windows: Windows 10 or later (preferably the latest updates)
- macOS: macOS 11.0 (Big Sur) or later
- Linux: Various distributions (Ubuntu 20.04, Debian 11, etc.)
Enhanced Collection Support with params Modifier
The params modifier now supports more collection types, including System.Span<T>, System.ReadOnlySpan<T>, and types implementing IEnumerable<T> with an Add method. Enhanced Collection Support with params Modifier improves flexibility by allowing Span<T> and similar types to be passed easily.
namespace CSharp13NewFeatures { public class ParamsExample { class Program { // Now accepts Span<int> static void AddNumbers(params Span<int> spans) { foreach (var span in spans) { Console.WriteLine(span); } } // Now accepts any IEnumerable<T> with an Add method public static void PrintNumbers(params IEnumerable<int> collection) { foreach (var item in collection) { Console.Write($"{item} "); } Console.WriteLine(); } static void Main() { int[] array = { 1, 2, 3 }; Span<int> span = array; AddNumbers(span); // Passes a Span<int> //Passing List of Integers List<int> list = new List<int> { 1, 2, 3, 4 }; PrintNumbers(list); //Passing a Queue Queue<int> queue = new Queue<int>(); queue.Enqueue(4); queue.Enqueue(5); PrintNumbers(queue); Console.ReadKey(); } } } }
Use Case: This is useful when you want to pass collections of various types, like Span<T> or ReadOnlySpan<T>, as parameters to methods, without worrying about the exact type passed. It’s ideal for cases where memory performance and efficiency are essential. Please click here for more detailed information about Enhanced Collection Support with the params Modifier.
Advanced Thread Synchronization with System.Threading.Lock
Introduces a new Lock type for improved thread synchronization, providing better performance and integration with the lock statement. Improves traditional lock by providing a System.Threading.Lock struct with async-friendly and performance-optimized locking. New Thread Synchronization with System.Threading.Lock is a better choice for thread synchronization, especially with async code.
namespace CSharp13NewFeatures { public class Program { // Shared resource (counter) static int counter = 0; // Lock object using the new System.Threading.Lock static readonly Lock lockObj = new Lock(); // Method to increment the counter static void IncrementCounter() { // Locking the critical section using the new Lock using (lockObj.EnterScope()) // EnterScope acquires the lock { int temp = counter; temp++; Thread.Sleep(10); // Simulating some work counter = temp; Console.WriteLine($"Counter: {counter}"); } } static void Main(string[] args) { // Create multiple threads Thread thread1 = new Thread(IncrementCounter); Thread thread2 = new Thread(IncrementCounter); // Start threads thread1.Start(); thread2.Start(); // Wait for threads to complete thread1.Join(); thread2.Join(); // Final counter value after both threads executes Console.WriteLine($"Final Counter Value: {counter}"); } } }
Use Case: This feature is designed for situations where high-performance thread synchronization is required, particularly when using async code and needing efficient management of shared resources across threads. For more detailed information about New Thread Synchronization with the System.Threading.Lock, please click here.
Partial Properties and Indexers
Introduces the partial keyword for properties and indexers, allowing for better code organization and shared logic separation, particularly in code generation scenarios. Partial Properties and Indexers help organize code for scenarios like code generation.
namespace CSharp13NewFeatures { public partial class Employee { public partial string Name { get; set; } //Partial Property public string? Address { get; set; } //Normal Property //Name Partial Property Implementation private string department = null!; public partial string Department { get => department; set => department = value.ToUpper(); // custom logic } } public partial class Employee { //Name Partial Property Implementation private string name = null!; public partial string Name { get => name; set => name = value.ToUpper(); // custom logic } public partial string Department { get; set; } //Partial Property public string? Designation { get; set; } //Normal Property } public class Program { public static void Main() { var obj = new Employee { Name = "Pranaya Rout", Address = "123 Street", Designation = "Developer", Department = "IT" }; Console.WriteLine($"Name: {obj.Name}, Address: {obj.Address}, Designation: {obj.Designation}, Department: {obj.Department}"); } } }
Use Case: This is particularly useful in scenarios involving code generation or dynamic assembly generation where properties or indexers must be split across multiple files but should still be treated as a unified entity. For more detailed information about Partial Properties and Indexers, please click here.
Improved Support for ref locals and unsafe in Async Methods and Iterators
Allows ref local variables and unsafe code to be used inside async methods and iterators, enhancing performance in performance-critical scenarios while adhering to safety rules. Improved support for ref and unsafe in Async Methods and Iterators allows for better performance in async/iterator scenarios.
namespace CSharp13NewFeatures { public class Program { // Simulated account balance static double accountBalance = 1000.00; static async Task Main(string[] args) { Console.WriteLine($"Initial Account Balance: ${accountBalance}"); // Perform an async withdrawal operation await WithdrawMoneyAsync(200); Console.WriteLine($"Final Account Balance: ${accountBalance}"); } // Async method to withdraw money and modify balance by reference static async Task WithdrawMoneyAsync(double amount) { await SimulateAsyncProcessing(); // Simulate some async operation (e.g., database call) // Modify the account balance by reference using ref ref double balance = ref GetAccountBalance(); ProcessWithdrawal(ref balance, amount); // Perform withdrawal } // Simulate async processing (e.g., database or network request) static async Task SimulateAsyncProcessing() { await Task.Delay(1000); // Simulate async work (e.g., contacting a database) Console.WriteLine("Processing withdrawal..."); } // Get reference to account balance static ref double GetAccountBalance() { return ref accountBalance; // Return the reference to the account balance } // Process the withdrawal and modify the balance static void ProcessWithdrawal(ref double balance, double amount) { if (balance >= amount) { balance -= amount; // Decrease balance by the withdrawal amount Console.WriteLine($"Withdrawal of ${amount} successful."); } else { Console.WriteLine("Insufficient funds."); } } } }
Use Case: This is useful in scenarios where performance is critical, such as modifying values in large datasets or complex calculations, while still utilizing async methods. It also helps maintain safety in potentially unsafe code execution. Please click here for more detailed information about Improved Support for ref locals and unsafe in Async Methods and Iterators.
Interface Support for Ref Struct Types
ref struct types, such as Span<T>, can now implement interfaces, increasing their reusability and compatibility, especially in generic programming. Interface Support for ref struct Types makes working with performance-critical types in generic or interface-based contexts easier.
namespace CSharp13NewFeatures { public interface IPrinter { void Print(); } public ref struct MySpanPrinter : IPrinter { private Span<int> _data; public MySpanPrinter(Span<int> data) => _data = data; public void Print() { foreach (var item in _data) Console.Write($"{item} "); } } public class Program { static void Main() { Span<int> numbers = stackalloc int[] { 1, 2, 3 }; var printer = new MySpanPrinter(numbers); printer.Print(); } } }
Use Case: This feature is useful when passing ref struct types (like Span<T>) through interfaces or generics, improving code flexibility and ensuring better reusability across multiple parts of your application, especially in performance-sensitive code. Please click here for more detailed information about Interface Support for ref struct Types.
Introduction of the field Keyword
The field keyword allows direct access to the compiler-generated backing field of auto-properties from within the property accessors. This streamlines property manipulation, making it easier to customize property behavior without needing explicit backing fields. Field Keyword improves direct access to auto-property backing fields, simplifying the property logic.
namespace CSharp13NewFeatures { public class Document { // Auto‐implemented property with compiler‐generated backing field public string Title { get => field; // ‘field’ refers to the backing field for Title set => field = value.Trim(); // direct access, trimming whitespace } } class Program { static void Main() { var doc = new Document(); doc.Title = " Quarterly Report "; Console.WriteLine($"[{doc.Title}]"); } } }
Use Case: The field keyword simplifies property manipulations, especially when auto-properties need to be extended with custom logic. It’s helpful in scenarios where you need to modify or access the backing field directly within the getter/setter of the property, without having to define a separate private field. Please click here for more detailed information about the Introduction of the field Keyword.
Enhanced Extension Members for Greater Flexibility
C# 13 expands the power of extension methods to include properties and indexers. This greatly enhances the flexibility of extending existing types without modifying their original code. Extension Members for Greater Flexibility extend existing types with new properties and indexers without modifying the original types.
namespace CSharp13NewFeatures { public static class StringExtensions { public static int WordCount(this string str) => str.Split(' ').Length; } public static class MathExtensions { public static int Square(this int number) => number * number; } public class Program { public static void Main() { string sentence = "Hello World from C# 13!"; Console.WriteLine(sentence.WordCount()); // Output: 5 int num = 4; Console.WriteLine(num.Square()); // Output: 16 } } }
Use Case: Extension members allow you to add functionality (properties and indexers) to existing types, such as string or int, without changing their original definitions. This is especially useful in legacy code or libraries where you can extend types without inheritance or modification of source code. For more detailed information about Enhanced Extension Members, please click here.
Overload Resolution Priority Attribute
This new attribute allows developers to control which method overload should take precedence during method resolution, providing better control over method resolution logic in complex APIs. Overload Resolution Priority Attribute enables more control over method overload resolution, which is useful in complex APIs.
using System.Runtime.CompilerServices; namespace CSharp13NewFeatures { public class Example { [OverloadResolutionPriority(1)] // Specifies priority public void Display(int number) { Console.WriteLine($"Integer: {number}"); } [OverloadResolutionPriority(0)] // Lower priority public void Display(string message) { Console.WriteLine($"Message: {message}"); } } public class Program { public static void Main() { var example = new Example(); example.Display(42); // Output: Integer: 42 example.Display("Hello"); // Output: Message: Hello } } }
Use Case: This feature is useful in scenarios with multiple overloads where the correct overload may not always be immediately clear. It allows developers to define which overload should be chosen explicitly, improving method resolution logic, particularly when dealing with APIs that contain many similar method signatures. For more detailed information about Overload Resolution Priority Attribute, please click here.
Primary Constructors for Records
C# 13 introduces primary constructors directly within record declarations, simplifying record initialization and making the code more concise. Primary Constructors for Records simplifies record initialization by enabling constructor parameters directly within the record declaration.
namespace CSharp13NewFeatures { public record Person(string FirstName, string LastName); public class Program { public static void Main() { var person = new Person("John", "Doe"); Console.WriteLine($"{person.FirstName} {person.LastName}"); // Output: John Doe } } }
Use Case: Primary constructors for records streamline the initialization process for record types. This is especially useful for creating immutable types or data transfer objects (DTOs), where the values are set at initialization and remain unchanged. For more detailed information about Primary Constructors for Records, please click here.
Default Interface Implementations for Static Members
Allows static members to have default implementations in interfaces. This provides greater flexibility and backward compatibility when designing interfaces that may evolve over time. Default Interface Implementations for Static Members give static default method implementations in interfaces, enabling backward compatibility and more flexible interface evolution.
namespace CSharp13NewFeatures { public interface IOperations { static int Add(int a, int b) => a + b; // Default static method } public class Operations : IOperations { // No need to explicitly implement Add method, as it's already provided } public class Program { public static void Main() { Console.WriteLine(IOperations.Add(5, 3)); // Output: 8 } } }
Use Case: This feature allows interfaces to evolve over time without breaking existing implementations. You can add static methods to interfaces and provide default implementations, ensuring that classes implementing those interfaces still function correctly without needing to implement these methods explicitly. Please click here for more detailed information about Default Interface Implementations for Static Members.
Learning and mastering C# 13’s new features will make you a more efficient and effective developer. Whether you’re working on simple projects or large enterprise applications, these new features improve performance, maintainability, and scalability. Understanding these improvements will help you stay up-to-date with modern C# development practices and ensure your codebase is robust, flexible, and easy to maintain.
In the next article, I will discuss Enhanced Collection Support with Params Modifier in C# with Examples. In this article, I explain the Overview of C# 13 New Features with Examples. I would like your feedback. Please post your feedback, questions, or comments about this article.