C# 14 shipped alongside .NET 10 in November 2025, and unlike some language releases, it's not just syntax sugar — a couple of these features change how you'd design an API from scratch. None of them require a rewrite. Here's what's actually worth adopting, and why.
- The
fieldkeyword gives auto-properties a backing field you can validate against, without declaring one by hand. - Extension members let you add properties, static members, and operators to types you don't own — not just methods.
- Null-conditional assignment (
?.on the left of=) removes a whole category of defensiveifchecks before writes. - These are additive language features — bump
LangVersionto14.0and adopt them incrementally in code you're already touching.
The field keyword
Before C# 14, adding validation or transformation logic to a property meant giving up the auto-property syntax entirely and declaring a private backing field by hand. The field keyword removes that trade-off — the compiler generates the backing field for you, and you can reference it inside a custom accessor.
public class Product
{
public required string Sku { get; set; }
public decimal Price
{
get => field;
set => field = value < 0
? throw new ArgumentOutOfRangeException(nameof(value), "Price cannot be negative.")
: value;
}
}
This is a small thing, but it's the kind of small thing that shows up constantly — a validated setter, a property that normalizes a string on write, a computed cache that invalidates on set. Previously all of those meant abandoning the auto-property syntax and its readability. Now they don't.
Extension members
Extension methods have been in C# since 3.0. Extension members generalize the idea — you can now add static members, instance properties, and operators to a type you don't own, grouped in an explicit extension block instead of scattered as loose static methods.
public static class EnumerableExtensions
{
extension<T>(IEnumerable<T> source)
{
public bool IsEmpty => !source.Any();
public IEnumerable<T> WhereNotNull() where T : class =>
source.Where(x => x is not null);
}
}
// usage
if (orders.IsEmpty) { /* ... */ }
var validCustomers = customers.WhereNotNull();
The practical win is discoverability. orders.IsEmpty reads like a first-class property in IntelliSense, instead of a static method call that only shows up if you already know it exists. For library authors extending BCL types or a shared internal SDK, this is a meaningfully nicer surface than a scattered pile of *Extensions static classes.
Null-conditional assignment
The null-conditional operator (?.) has worked on reads since C# 6. C# 14 extends it to assignment — the right-hand side only evaluates, and the assignment only happens, if the left-hand side isn't null.
// Before
if (customer?.Profile != null)
{
customer.Profile.LastSeenAt = DateTime.UtcNow;
}
// C# 14
customer?.Profile?.LastSeenAt = DateTime.UtcNow;
It's a small syntactic change, but it removes a specific, recurring shape of defensive code — the null-check that exists purely to guard a single assignment. Worth calling out: the right-hand side is only evaluated when the assignment actually happens, so don't rely on a method call there for a side effect you need unconditionally.
Implicit span conversions, quietly better
.NET 10's JIT and C# 14 together make Span<T> and ReadOnlySpan<T> conversions from arrays and strings more consistent across method overload resolution, which mostly matters if you're writing performance-sensitive code that leans on spans. If you haven't adopted Span<T> in hot paths yet, that's a separate decision — but if you have, upgrading gets you fewer overload-resolution surprises for free.
<LangVersion>14.0</LangVersion> (or latest), and let the team pick these up during normal code review rather than a dedicated migration sprint.