C# 14 is just around the corner, and it ships alongside .NET 10, the next big milestone for the .NET ecosystem.
The official release of .NET 10 is scheduled for November 2025, and it will be a Long-Term Support (LTS) release — meaning you’ll get free patches and updates for three years.
Even though the final release isn’t here yet, the Release Candidate (RC) for .NET 10 is already available. That means you can start experimenting with C# 14 today, try out its new language features, and get a head start on upgrading your apps.
In this post, we’ll explore the 3 most practical features of C# 14 for everyday developers, with clear explanations, before-and-after examples, and practical tips for when to use each feature.
Null-Conditional Assignment
Null-checking before an assignment is one of the most repetitive tasks in C#. Previously, you had to guard every assignment with an if
statement, which bloated code and added indentation. This feature removes that noise while still being safe.
// Before C#14
if (customer is not null)
{
customer.Order = GetCurrentOrder();
}
C# 14 now allows you to use ?.
and ?[]
on the left-hand side of an assignment (or compound assignment like +=
). If the receiver is null
, the assignment is skipped with no exceptions thrown.
// With C#14
customer?.Order = GetCurrentOrder();
The right side of the =
operator is evaluated only when the left side isn’t null. If customer
is null, the code doesn’t call GetCurrentOrder
.
Caveats
- The right-hand side won’t be evaluated if the receiver is null — don’t put important side effects there if they must always run.
- Doesn’t work with increment/decrement operators (
++
or--
). Only assignment and compound assignment are supported.
The field
Keyword
Up until now, if you wanted to enforce a rule in a property setter (like preventing null
assignments), you had to create a separate backing field and write both accessors yourself, losing the simplicity of auto-properties.
In the example below, we’re forced to introduce a _message
field just so we can add a null
check in the setter.
// Before C#14
private string _message;
public string Message
{
get => _message;
set => _message = value ?? throw new ArgumentNullException(nameof(value));
}
C# 14 introduces the field
keyword, which allows you to reference the compiler-generated backing field of an auto-property directly. This means you can add logic to a property accessor without having to manually declare and manage a private field.
// With C#14
public string Message
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
The field
token is replaced by the compiler with a synthesized backing field. You still get a fully functioning auto-property, but with custom accessor logic.
Caveats
- If your type already has a member named
field
, this could be confusing. You can disambiguate by using@field
orthis.field
, or (better) rename the existing member. - Remember that
field
only exists inside the property accessor body — it’s not available anywhere else.
Extension Members and the Extension Syntax
Extension methods were a big deal when they were added in C# 3.0 — they let you add functionality to existing types (like LINQ does) without modifying them.
But methods were all you could add — no properties, no operators, no static helpers. C# 14 removes those limitations.
C# 14 introduces a new syntax for extension members, letting you do more than just add methods.
With the new extension
blocks, you can now declare:
-
Extension properties — so you can call
obj.MyProperty
on types you don’t own. - Static extension members — members that appear as if they’re part of the type itself.
-
User-defined operators — adding custom operators like
+
,-
, or even comparison operators to existing types.
This makes extensions feel more natural, letting you express domain logic as if you had direct control over the type.
// Before C#14
public static class ListExtensions
{
public static bool IsEmpty<T>(this List<T> list)
=> list.Count == 0;
public static List<T> Combine<T>(this List<T> list, List<T> other)
=> list.Concat(other).ToList();
}
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };
if (list1.IsEmpty()) { /* ... */ }
var combined = list1.Combine(list2);
// With C#14
public static class ListExtensions
{
// Instance-style extension members
extension<T>(List<T> list)
{
public bool IsEmpty => list.Count == 0;
}
// Static (type-level) extension members
extension<T>(List<T>)
{
public static List<T> operator +(List<T> first, List<T> second)
=> first.Concat(second).ToList();
public static List<T> Empty => new();
}
}
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };
if (list1.IsEmpty) { /* ... */ }
var combined = list1 + list2;
var emptyList = List<int>.Empty;
Extensions now look and behave like native members of the type.
When to Use
- Adding semantic helpers to framework types (like
.IsEmpty
or.FullName
). - Providing type-level utilities (like
List<T>.Empty
orDateOnly.Today
). - Defining domain-specific operators (e.g.
+
for combining domain objects).
Caveats
- Keep it intuitive — avoid adding operators or properties that surprise readers.
- Remember that extension members are still syntactic sugar — they don’t actually modify the type.
- Consider discoverability: too many extension blocks scattered across namespaces can confuse users.
Conclusion
C# 14 is a refinement release that focuses on making everyday code cleaner and more expressive.
With null-conditional assignment, you eliminate repetitive null checks. With the field
keyword, you get custom logic on auto-properties without sacrificing simplicity. And with extension members, you can make your code feel more natural by adding properties, operators, and utilities to types you don’t own.
These features may seem small at first glance, but they add up to a more elegant developer experience — fewer lines of boilerplate, less ceremony, and more readable code.
For more features and full details on what’s new in C# 14, check out “What’s new in C# 14” on Microsoft Learn.
Top comments (0)