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):
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(); } }
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):
var item = new Item { Name = " lamp " }; Console.WriteLine($"[{item.Name}]"); class Item { public string Name { get; set => field = value.Trim(); } = ""; }
[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):
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; } }
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):
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; }
8 2
nameof with unbound generic types
nameof now accepts generic types without type arguments:
Console.WriteLine(nameof(List<>)); Console.WriteLine(nameof(Dictionary<,>));
List Dictionary
Modifiers on simple lambda parameters
Lambdas can use out, ref, in and scoped on parameters without writing out their types (lesson 47):
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);
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:
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; }
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
| Feature | Version | Example | Lesson |
|---|---|---|---|
| Primary constructors for classes | 12 | class Service(ILogger log) { … } | 32 |
| Collection expressions | 12 | int[] a = [1, 2, .. other]; | 25, 30 |
| Alias any type | 12 | using Point = (int X, int Y); | 59 |
| Default lambda parameters | 12 | var f = (int x = 1) => x; | 47 |
params collections | 13 | void Log(params ReadOnlySpan<string> parts) | 21 |
New Lock type | 13 | lock (_gate) { … } | 58 |
\e escape | 13 | "\e[1mbold\e[0m" (terminal escape codes) | — |
| Partial properties | 13 | public partial string Name { get; set; } | — |
^ index in object initialisers | 13 | new 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.
[Loves C#. And coffee.] 4 words