Exploring .NET 10 and C# 13: New Features and Enhancements

Posted by:

|

On:

|

, ,

As of February 2025.NET 10 is slated for release in November 2025, bringing a wave of enhancements that promise to refine performance, developer productivity, and cross-platform capabilities. While the official feature set is still under development, early previews and community discussions highlight several anticipated improvements. These enhancements are expected to build upon the foundations of .NET 9, introducing new language featuresruntime optimizations, and expanded support for modern development workflows. From refined concurrency models to enhanced AOT (Ahead-of-Time) compilation, .NET 10 is shaping up to be a significant step forward for developers across webcloud, and desktop applications.

C# 13 Enhancements

C# 13, the latest evolution of the programming language that accompanies .NET 10, introduces a range of exciting features designed to enhance developer productivity and elevate code quality. These enhancements not only streamline common coding tasks but also promote best practices, making it easier for developers to write cleanefficient, and maintainable code.

Enhanced params Collections

In previous versions of C#, the params keyword was restricted to arrays, limiting its flexibility in method signatures. With the introduction of C# 13, this capability has been significantly expanded, allowing params to work with a variety of collection types, including List<T>Span<T>, and IEnumerable<T>. This enhancement not only simplifies method definitions but also provides developers with greater flexibility in how they can pass multiple arguments, leading to cleaner and more expressive code.

✔️C# 13 Example: Using params with List<T> and Span<T>

This code demonstrates the use of the params keyword in C# 13, allowing methods to accept variable numbers of arguments using different collection types.

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
// Method accepting params as List<T>
static void PrintNumbers(params List<int> numbers)
{
Console.WriteLine("Numbers (List<T>): " + string.Join(", ", numbers));
}

// Method accepting params as Span<T>
static void PrintSpan(params Span<int> numbers)
{
Console.WriteLine("Numbers (Span<T>): " + string.Join(", ", numbers.ToArray()));
}

// Method accepting params as IEnumerable<T>
static void PrintEnumerable(params IEnumerable<int> numbers)
{
Console.WriteLine("Numbers (IEnumerable<T>): " + string.Join(", ", numbers));
}

static void Main()
{
// Using params with List<T>
PrintNumbers(new List<int> { 1, 2, 3, 4, 5 });

// Using params with Span<T>
int[] spanData = { 6, 7, 8, 9, 10 };
PrintSpan(spanData);

// Using params with IEnumerable<T>
PrintEnumerable(11, 12, 13, 14, 15);
}
}
  • PrintNumbers Method (List<T>): Allows passing a List<int> instead of just an array.
  • PrintSpan Method (Span<T>): Uses Span<T> to efficiently process elements in memory without allocations.
  • PrintEnumerable Method (IEnumerable<T>): Enables using params with IEnumerable<T> for more flexibility.

Expected Output

Numbers (List<T>): 1, 2, 3, 4, 5
Numbers (Span<T>): 6, 7, 8, 9, 10
Numbers (IEnumerable<T>): 11, 12, 13, 14, 15

New Lock Object

To improve thread synchronization, C# 13 introduces the System.Threading.Lock type. This new lock object provides a more robust and readable approach to handling concurrency, replacing the traditional use of the lock statement with an object type.

❌ C# Example: The Old Way

  • Doesn’t support async.
  • Can cause deadlocks if used incorrectly.
private static readonly object _lockObj = new();

lock (_lockObj)
{
sharedResource++;
}

✔️ C# 13: The New Way

using (await myLock.AcquireAsync())
{
sharedResource++;
}
  • Async-compatible.
  • Less prone to deadlocks.
  • Cleaner, more readable syntax.

Expected Output

Resource updated to 1 by 1
Resource updated to 2 by 2
Resource updated to 3 by 3
Resource updated to 4 by 4
Resource updated to 5 by 5
Final Resource Value: 5

New Escape Sequence

A new escape sequence, \e, has been added to represent the ESC (escape) character (U+001B). This addition simplifies the inclusion of the escape character in strings.

❌ C# Example: The Old Way

  • Less readable and harder to remember.
  • Verbose and unnecessary for simple escape sequences.
// Using Unicode
string escMessage = "\u001B[31mThis text is red!\u001B[0m";

// Using Byte Array
string escMessage = $"{(char)0x1B}[31mThis text is red!{(char)0x1B}[0m";

✔️ C# 13 Example: The New Way

using System;

class Program
{
static void Main()
{
// Using \e to represent the ESC character
string escMessage = "\e[31mThis text is red!\e[0m";

// Displaying the ESC character as a hexadecimal value
Console.WriteLine($"ESC Character in Hex: {(int)'\e':X}");

// Printing the ESC sequence (in terminals that support ANSI escape codes)
Console.WriteLine(escMessage);
}
}
  • \e represents the ESC (U+001B) character.
  • "[31m" is an ANSI escape code that changes text color to red.
  • "[0m" resets the formatting back to default.
  • If your terminal supports ANSI escape codes, this will print “This text is red!” in red.

✔️ Displaying the ESC Character in Hexadecimal (U+001B)

Converts \e to its Unicode value 1B (hex).

Console.WriteLine($"ESC Character in Hex: {(int)'\e':X}");

Partial Properties and Indexers

Building upon the concept of partial methods, C# 13 allows for partial properties and indexers. This feature enables developers to separate the declaration and implementation of properties and indexers across different parts of a partial class, enhancing code organization and collaboration.

✔️ Example: Partial Property Implementation

This code example demonstrates the separation of Declaration & Implementation: One part defines the property signature, while another handles getter/setter logic.

1️⃣ Declare the Property in One Part of the Partial Class

public partial class Product
{
// Declaration of a partial property (only the signature)
public partial string Name { get; set; }
}

2️⃣ Implement the Property in Another Part

public partial class Product
{
private string _name = "Default Product";

// Implement the partial property
public partial string Name
{
get => _name;
set
{
if (!string.IsNullOrWhiteSpace(value))
{
_name = value;
}
}
}
}

3️⃣ Usage

class Program
{
static void Main()
{
Product product = new();
Console.WriteLine(product.Name); // Output: Default Product

product.Name = "Laptop";
Console.WriteLine(product.Name); // Output: Laptop

product.Name = ""; // Invalid, ignored by the setter
Console.WriteLine(product.Name); // Output: Laptop
}
}

Implicit Index Access in Object Initializers

C# 13 permits the use of the index operator (e.g., [^1]) within object initializers, allowing for more concise and expressive initialization of collections and arrays.

What is the Index Operator? The ^ operator lets you index from the end of a collection:

  • ^1 → Refers to the last element.
  • ^2 → Refers to the second-to-last element.

❌ Code Example: Old way

Problem: You cannot modify [^1] in the initializer itself.

var numbers = new List<int> { 1, 2, 3, 4 };
numbers[^1] = 99; // Manually setting last element

✔️ Code Example: New Way

Now, you can set the last element directly in the initializer:

using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
// Initializing and setting last element using ^1
var numbers = new List<int> { 1, 2, 3, 4, [^1] = 99 };

Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 2, 3, 99
}
}

✔️ Example using Arrays

Works with arrays just like lists.

var words = new string[]
{
"Hello",
"World",
"C#",
"Rocks!",
[^1] = "🔥" // Changing last element to "🔥"
};

Console.WriteLine(string.Join(" ", words)); // Output: Hello World C# 🔥

✔️ Code Example: Object Initialization with Lists

Applies to properties inside objects during initialization.

class ShoppingList
{
public List<string> Items { get; set; } = new() { "Milk", "Eggs", "Bread", [^1] = "Gluten-Free Bread" };
}

class Program
{
static void Main()
{
var shoppingList = new ShoppingList();
Console.WriteLine(string.Join(", ", shoppingList.Items));
// Output: Milk, Eggs, Gluten-Free Bread
}
}

Blazor Enhancements

Blazor, the powerful framework for building interactive web UIs with .NET, is expected to receive notable enhancements in .NET 10. While Microsoft has yet to officially announce the full feature set, the developer community has highlighted key areas for improvement. Among the most anticipated updates are more reliable hot reloading, reducing the need for full application restarts and streamlining the development experience. Additionally, there is growing interest in performance optimizations for Blazor WebAssembly, improved support for server-side rendering (SSR), and enhancements to Blazor Hybrid applications, which bridge web and native platforms. With .NET 10’s focus on efficiency and scalabilityBlazor is poised to become an even more versatile choice for modern web application development.

Docker Integration

The .NET 10 milestone on GitHub outlines plans for enhanced Docker support, aiming to further streamline the developmentdeployment, and management of containerized applications. These improvements are expected to simplify the creation and optimization of Docker images, reducing build times and improving runtime efficiency for .NET applications. Developers can anticipate better integration with cloud-native workflows, improved multi-stage builds, and enhanced support for minimal and secure base images. As containerization continues to be a key trend in modern application development, .NET 10’s focus on Docker will provide a more seamless and efficient experience for deploying applications in cloud and hybrid environments.

Please note that these features are based on current previews and community discussions. The final feature set for .NET 10 will be confirmed upon its official release. For the most up-to-date information, refer to the official .NET Blog and the .NET GitHub repository.