Modern C# Features Every .NET Developer Should Know
C# continues to evolve as one of the most powerful and developer-friendly programming languages in the world. With every annual release, Microsoft adds features that make writing clean, efficient, and modern code much easier.
In this article, we'll summarize the features across recent C# versions (12 through the current 14) that every .NET developer should know. Whether you're building enterprise applications, APIs, or cloud-native solutions, these updates will help you write less code, improve performance, and reduce bugs.
đź“– Table of Contents
- 1. C# Primary Constructors for Classes
- 2. C# Collection Expressions Example
- 3. Inline Array Literals and Span<T> in C#
- 4. Using Directives and Aliases in C#
- 5. C# Interceptors (Preview Feature)
- 6. Enhanced Pattern Matching in C#
- 7. Default Lambda Parameters in C#
- 8. Improvements in nameof Operator
- 9. Raw String Literals in C#
- 10. Performance-Oriented Features in C#
- 11. What's New in C# 13
- 12. What's New in C# 14
- Final Thoughts
- Key Takeaways
- What’s Next?
Which C# version introduced what?
| Feature | C# Version | .NET Version |
|---|---|---|
| Primary constructors for classes/structs | C# 12 | .NET 8 |
Collection expressions ([1, 2, 3]) |
C# 12 | .NET 8 |
Inline array literals for Span<T> |
C# 12 | .NET 8 |
| Default lambda parameters | C# 12 | .NET 8 |
| Interceptors (preview) | C# 12 | .NET 8 |
params collections (not just arrays) |
C# 13 | .NET 9 |
New Lock type / lock-statement pattern |
C# 13 | .NET 9 |
| Implicit index access in object initializers | C# 13 | .NET 9 |
field keyword for auto-properties (stable) |
C# 14 | .NET 10 |
| Extension members (extension blocks) | C# 14 | .NET 10 |
Null-conditional assignment (?. on the left of =) |
C# 14 | .NET 10 |
| First-class span conversions | C# 14 | .NET 10 |
The sections below cover the C# 12-era features this post originally focused on, followed by what's new in C# 13 and C# 14.
1. C# Primary Constructors for Classes
C# now supports primary constructors directly in classes (earlier available only for records). This reduces boilerplate and makes class initialization more concise.
public class Product(string name, decimal price)
{
public string Name { get; } = name;
public decimal Price { get; } = price;
}
âś… No need to write manual constructors.
âś… Great for immutable data models.
2. C# Collection Expressions Example
C# introduces collection expressions to make working with arrays and collections much cleaner.
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Alice", "Bob", "Charlie"];
This is similar to JavaScript or Python syntax and improves readability.
3. Inline Array Literals and Span<T> in C#
Performance-focused developers will love this. You can now initialize Span<T> and ReadOnlySpan<T> using inline array literals.
Span<int> values = [1, 2, 3, 4];
ReadOnlySpan<char> letters = ['a', 'b', 'c'];
This avoids unnecessary allocations and is great for high-performance applications.
4. Using Directives and Aliases in C#
You can simplify code organization with file-scoped using directives and aliases.
global using System.Text.Json;
using ProjectModels = MyApp.Core.Models;
âś… Helps manage namespaces in large projects.
5. C# Interceptors (Preview Feature)
Interceptors allow you to inject logic before a method call executes, making it possible to customize APIs and behaviors without modifying the original method.
[InterceptsLocation("MyFile.cs", 25, 10)]
public static void InterceptDoSomething()
{
Console.WriteLine("Intercepted!");
}
đź’ˇ Think of it as a compile-time AOP (Aspect-Oriented Programming) feature.
⚠️ Interceptors remain a preview-only feature through C# 13/.NET 9 - they're primarily meant to be emitted by source
generators (e.g. for compile-time interception of specific call sites) rather than hand-written in application code,
and the feature still requires an explicit opt-in MSBuild property (InterceptorsPreviewNamespaces or similar,
depending on SDK version). Don't rely on this for production code paths without checking the current preview status
for the SDK version you're targeting.
6. Enhanced Pattern Matching in C#
C# keeps expanding its pattern matching capabilities, making code more expressive and readable.
if (obj is Product { Price: > 100 and < 500 })
{
Console.WriteLine("Mid-range product");
}
âś… Cleaner than nested if conditions.
7. Default Lambda Parameters in C#
Lambda expressions can now have default parameter values, improving flexibility.
Func<int, int, int> add = (a, b = 10) => a + b;
Console.WriteLine(add(5)); // 15
Console.WriteLine(add(5, 20)); // 25
8. Improvements in nameof Operator
The nameof operator now works with method parameters more intuitively, helping with logging, exception handling, and diagnostics.
void ProcessOrder(int orderId)
{
if (orderId <= 0)
throw new ArgumentException($"Invalid {nameof(orderId)}");
}
9. Raw String Literals in C#
Working with JSON, SQL, or multiline text is now much easier with raw string literals.
string query = """
SELECT *
FROM Products
WHERE Price > 100
ORDER BY Name;
""";
âś… No need for escape sequences.
âś… Makes code more readable.
10. Performance-Oriented Features in C#
- Optimized
foreachloops for better iteration performance. - Reduced memory allocations in many BCL (Base Class Library) APIs.
- Better JIT optimizations in .NET runtime for modern processors.
These features make C# one of the fastest managed languages for enterprise and cloud-scale applications.
11. What's New in C# 13
C# 13 (shipped with .NET 9) added a smaller, more incremental set of features compared to C# 12, but a few are worth adopting immediately:
// params collections - not just T[] anymore
void LogAll(params IEnumerable<string> messages) { /* ... */ }
LogAll("one", "two", "three"); // still works
LogAll(["one", "two", "three"]); // also works - any collection type, not just arrays
// New Lock type - a lighter, faster alternative to `lock (object)`
private readonly Lock _lock = new();
lock (_lock)
{
// the compiler recognizes System.Threading.Lock and emits a more efficient
// acquire/release pattern than the classic Monitor-based lock(object)
}
// Implicit index access in object initializers
int[] years = new int[4];
var config = new SomeType
{
Values = { [^1] = 2026 } // ^1 (from-the-end index) now works inside an initializer
};
params collections in particular is worth adopting broadly - any existing params T[] API becomes more flexible for
callers without you changing its signature to something exotic, and the new Lock type is a drop-in win for any hot
synchronization path.
12. What's New in C# 14
C# 14 (shipped with .NET 10, the current LTS release) is a bigger jump, with two headline features:
Extension members - a new extension block syntax that lets you add not just methods, but also properties and
static members, to an existing type - something classic extension methods never supported:
public static class StringExtensions
{
extension(string s)
{
public bool IsPalindrome => s.SequenceEqual(s.Reverse());
public static string Empty => string.Empty;
}
}
"level".IsPalindrome; // true - reads like a real property, not a method call
Null-conditional assignment - you can now use ?. on the left-hand side of an assignment, so you no longer need a
separate null check before setting a property:
// before C# 14
if (customer != null)
{
customer.LastLogin = DateTime.UtcNow;
}
// C# 14
customer?.LastLogin = DateTime.UtcNow;
A couple of smaller but useful additions round out C# 14:
- The
fieldkeyword (preview since C# 13) is now stable, letting you add logic to an auto-property's getter/setter without declaring a full backing field:public string Name { get => field; set => field = value?.Trim(); }. - First-class span conversions make
Span<T>/ReadOnlySpan<T>participate in overload resolution and implicit conversions more consistently with arrays, reducing the number of places you need explicit casts when working with theSpan<T>APIs introduced back in C# 12/.NET 8 (see section 3 above).
Final Thoughts
C# is no longer just a “Windows-only” language—it’s now a modern, cross-platform, cloud-ready powerhouse. With features like primary constructors, collection expressions, extension members, and null-conditional assignment, developers can write cleaner and more efficient code than ever before.
👉 If you’re a .NET developer, mastering these features across C# 12 through C# 14 will set you apart and keep your skills future-proof.
📌 Key Takeaways
- Write less boilerplate with primary constructors and the stable
fieldkeyword. - Boost performance using
Span<T>, inline literals, and first-class span conversions. - Improve readability with collection expressions, raw string literals, and extension members.
- Simplify null-checks with null-conditional assignment, and synchronization with the new
Locktype.
🚀 What’s Next?
If you’re interested in learning more:
- Explore the official .NET Blog.
- Try these features in your .NET 9 / .NET 10 projects - .NET 10 is the current LTS release.
Once you're comfortable with the language-level features above, a natural next step is seeing how they show up in the ASP.NET Core framework code itself. I've written a series digging into that internals from the ground up:
- Understanding CreateBuilder() method in AspNetCore
- Exploring ServiceCollection in .NET
- Exploring ServiceDescriptor in .NET
- Exploring ServiceProvider in .NET
- Authentication and Authorization in AspNetCore
Thanks for reading! If you found this useful, consider sharing it with your developer community to spread the word about the latest C# features.
Exploring ServiceProvider in .NET
Exploring ServiceDescriptor in .NET
Exploring ServiceCollection in .NET
Authentication and Authorization in AspNetCore
Authentication and Authorization in Asp.Net WebApi
Authentication and Authorization in Asp.Net MVC
Understanding Authentication and Authorization in Asp.Net WebForms
Exploring AddControllers() method in AspNetCore
Understanding CreateBuilder() method in AspNetCore
Setup and debug microsoft aspnetcore source code
