Skip to content
Happy Programming Guide
Start learning
C#

C# 14 Extension Members Explained

Extension members let you add properties, static members and operators to types you do not own, not just methods. The new extension block syntax, how it compares with the old, and when to use each.

Keys of a mechanical keyboard seen up close

Extension methods have been in C# since version 3: a static method with a this parameter that you can call as if it belonged to the type. C# 14 generalises the idea. An extension block can declare methods, properties, static members and operators against a type, and it groups them under one receiver so you write the type once. The old syntax still works; the new one covers the cases it never could.

The old way, for reference#

CSHARP
public static class StringExtensions
{
    public static bool IsBlank(this string? s) => string.IsNullOrWhiteSpace(s);
}

"   ".IsBlank();     // true

A static method in a static class, with this on the first parameter. It could only ever be a method, and the receiver was repeated on every member.

The extension block#

CSHARP
public static class StringExtensions
{
    extension(string? s)                       // the receiver, named once
    {
        public bool IsBlank => string.IsNullOrWhiteSpace(s);        // a property
        public string OrDefault(string fallback) => IsBlank ? fallback : s!;   // a method
        public int WordCount => IsBlank ? 0 : s!.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
    }
}

"   ".IsBlank;                 // true - no brackets: it is a property
"hello world".WordCount;       // 2
name.OrDefault("anonymous");

Inside the block, the receiver name, s, is available to every member. Members are declared as if they were instance members of the type. The containing class must still be static and non-nested, as before.

Extension properties#

This is the feature most requested for fifteen years. A computed value that reads naturally as a property rather than a method call:

CSHARP
public static class EnumerableExtensions
{
    extension<T>(IEnumerable<T> source)
    {
        public bool IsEmpty => !source.Any();
        public T? FirstOrNull => source.FirstOrDefault();
    }
}

if (orders.IsEmpty) { ... }

Extension properties are get-only or get-and-set through accessors that call your code; there is no storage. They cannot add a field to the type, because the type’s layout is fixed. A setter on an extension property has to write somewhere you provide.

Generic receivers#

The block can be generic, with type parameters declared on the block and inferred at the call site, exactly as generic extension methods were:

CSHARP
extension<TKey, TValue>(Dictionary<TKey, TValue> dict) where TKey : notnull
{
    public TValue GetOr(TKey key, TValue fallback) => dict.TryGetValue(key, out var v) ? v : fallback;
}

Static extension members#

Leave the receiver unnamed and members become static, callable on the type itself:

CSHARP
public static class DateOnlyExtensions
{
    extension(DateOnly)
    {
        public static DateOnly Today => DateOnly.FromDateTime(DateTime.Today);
        public static DateOnly Parse(string s, string format) => DateOnly.ParseExact(s, format);
    }
}

var d = DateOnly.Today;            // as if the type had always had it

Useful for factory methods and constants on types you cannot modify.

Extension operators#

CSHARP
public static class VectorExtensions
{
    extension((double X, double Y))
    {
        public static (double X, double Y) operator +((double X, double Y) a, (double X, double Y) b)
            => (a.X + b.X, a.Y + b.Y);
    }
}

var sum = (1.0, 2.0) + (3.0, 4.0);     // (4, 6)

Operators are declared as static members inside the block. This was impossible before: operators had to be defined inside the type.

How resolution works#

  • A real instance member on the type always wins over an extension member with the same name. Extensions fill gaps; they cannot override.
  • Extension members are found the same way extension methods were: the containing static class must be in scope, via using the namespace.
  • Two extensions in scope with the same name and receiver produce an ambiguity error at the call site, as before.
  • The block form and the this-parameter form are interchangeable for methods: the compiler generates the same static method, so existing callers, reflection and other languages see no difference.

Old syntax or new#

Situation Use
One or two methods on a type Either; the old form is shorter
Several members sharing a receiver Block: the receiver is written once
A property, static member or operator Block: the old form cannot do it
A library that must compile on older SDKs Old form

There is no need to convert existing extension methods. Mixing both forms in one static class is allowed.

Questions people ask#

Does this need .NET 10?

It needs the C# 14 compiler, which ships with the .NET 10 SDK. With LangVersion set to 14 it compiles for older target frameworks too, since the generated code is ordinary static methods.

Can extension members be private or internal?

They follow the normal accessibility rules of the containing static class and the member itself. Internal extensions for a library’s own use are common.

Can I extend an interface?

Yes, as with extension methods. All implementers get the members.

Do extension members show up in IntelliSense?

Yes, once the namespace is imported, marked with the extension icon as methods always were.

Where to go next#

What's new in C# 14 and .NET 10Read next

Keep reading

C#

Introduction to C#

What C# is, what .NET provides, where the language is used, and your first program — plus how it compares with Java,…

4 min read

C#

C# Events and Delegates

What a delegate is, how events build on them, Action and Func, and the two mistakes that cause memory leaks and null…

5 min read

C#

C# Design Patterns

The design patterns that earn their place in C#: strategy, factory, repository, decorator, observer and options — with the language features that…

4 min read

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *