What's New in C# 14

C# evolves every year, and each release removes a little more ceremony. This lesson gathers the C# 14 features (shipped with .NET 10 in November 2025) in one place, followed by a quick tour of the C# 12 and 13 features you've used throughout the course — useful if you're returning to C# after a few years, or reading code written for older versions.

Extension members

The headline feature. extension blocks declare extension properties and static extension members, not just methods (lesson 46):

C#
int[] data = [3, 1, 4];
Console.WriteLine(data.IsEmpty);
Console.WriteLine(data.Total);
Console.WriteLine(Array.Repeat("hi", 3).Length);

static class Ext
{
    extension(int[] values)
    {
        public bool IsEmpty => values.Length == 0;
        public int Total => values.Sum();
    }

    extension(Array)
    {
        public static T[] Repeat<T>(T value, int count) => Enumerable.Repeat(value, count).ToArray();
    }
}
Output
False
8
3

The field keyword

Inside a property accessor, field refers to the compiler-generated backing field, so you can add logic to one accessor without declaring a field yourself (lesson 33):

C#
var item = new Item { Name = "  lamp  " };
Console.WriteLine($"[{item.Name}]");

class Item
{
    public string Name
    {
        get;
        set => field = value.Trim();
    } = "";
}
Output
[lamp]

If your class already has a member called field, refer to it as this.field or @field inside accessors.

Null-conditional assignment

?. and ?[] can now appear on the left of an assignment. The assignment happens only if the object isn't null — and the right-hand side isn't even evaluated otherwise (lesson 43):

C#
Customer? found = new();
Customer? missing = null;

found?.Email = Normalise("ADA@EXAMPLE.COM");
missing?.Email = Normalise("never@called.com");
Console.WriteLine(found?.Email);

static string Normalise(string s)
{
    Console.WriteLine($"normalising {s}");
    return s.ToLowerInvariant();
}

class Customer { public string? Email { get; set; } }
Output
normalising ADA@EXAMPLE.COM
ada@example.com

Implicit span conversions

Arrays convert implicitly to Span<T> and ReadOnlySpan<T> in more situations, including as the receiver of extension methods and in generic type inference. Span-based APIs now "just work" with arrays (lesson 30):

C#
int[] numbers = [5, 3, 8];
Console.WriteLine(Largest(numbers));
Console.WriteLine(numbers.AsSpan().IndexOf(8));

static int Largest(ReadOnlySpan<int> values)
{
    int max = int.MinValue;
    foreach (int v in values) max = Math.Max(max, v);
    return max;
}
Output
8
2

nameof with unbound generic types

nameof now accepts generic types without type arguments:

C#
Console.WriteLine(nameof(List<>));
Console.WriteLine(nameof(Dictionary<,>));
Output
List
Dictionary

Modifiers on simple lambda parameters

Lambdas can use out, ref, in and scoped on parameters without writing out their types (lesson 47):

C#
TryGet<int> parse = (string text, out int value) => int.TryParse(text, out value);   // before
TryGet<int> shorter = (text, out value) => int.TryParse(text, out value);             // C# 14

Console.WriteLine(shorter("41", out int n) ? n + 1 : -1);

delegate bool TryGet<T>(string text, out T value);
Output
42

User-defined compound assignment

Types can now overload compound assignment operators such as += directly, as instance methods that update the object in place. Previously x += y always meant x = x + y, which creates a new object — wasteful for large values like matrices or big buffers:

C#
var total = new Accumulator();
total += 5;
total += 3;
total += 10;
Console.WriteLine(total.Value);

class Accumulator
{
    public int Value { get; private set; }
    public void operator +=(int amount) => Value += amount;
}
Output
18

Partial constructors and events

Constructors and events can now be declared partial, completing the set started by partial methods and (in C# 13) partial properties. This is mainly for source generators, which write one half of a member while you write the other.

Single-file apps (.NET 10)

Not a language feature but a big tooling change that shipped alongside C# 14: dotnet run app.cs runs a single file with no project, with #:package, #:property and #:sdk directives and Unix shebang support (lessons 3–4 and 60).

Catching up: C# 12 and 13

FeatureVersionExampleLesson
Primary constructors for classes12class Service(ILogger log) { … }32
Collection expressions12int[] a = [1, 2, .. other];25, 30
Alias any type12using Point = (int X, int Y);59
Default lambda parameters12var f = (int x = 1) => x;47
params collections13void Log(params ReadOnlySpan<string> parts)21
New Lock type13lock (_gate) { … }58
\e escape13"\e[1mbold\e[0m" (terminal escape codes)—
Partial properties13public partial string Name { get; set; }—
^ index in object initialisers13new Buffer { Data = { [^1] = 9 } }—

And earlier highlights

Still relatively recent: records and init-only properties (C# 9), file-scoped namespaces and global usings (10), raw string literals, list patterns, required members and generic math (11). If you learned C# before about 2020, these are the features most worth catching up on.

What comes next

.NET 11 and C# 15 are due in November 2026, with previews available beforehand. Because this course targets .NET 10 — the long-term-support release, supported until November 2028 — everything you've learned stays current. To try preview features in a project, set <LangVersion>preview</LangVersion> with a preview SDK; the official "What's new in C#" documentation lists each version's features as they're finalised.

Try it yourself

Combine three C# 14 features: an extension property, the field keyword and null-conditional assignment.

Try it yourself · Program.cs
Edit the code, copy it into Program.cs and run it with dotnet run Program.cs (.NET 10 or later). How to set up .NET
Output
[Loves C#. And coffee.] 4 words