Extension Members

Extension members let you add methods and properties to existing types — even ones you don't own, like string, int or IEnumerable<T> — without changing or inheriting from them. They're how LINQ adds Where and Select to every collection. C# 14 extends the classic extension methods with extension blocks, which also support properties and static members.

Extension methods

An extension method is a static method in a static class whose first parameter has the this modifier. It can then be called as if it were an instance method of that parameter's type:

C#
string title = "  the hobbit ";
Console.WriteLine(title.ToTitleCase());
Console.WriteLine("racecar".IsPalindrome());
Console.WriteLine(42.IsEven());

static class StringExtensions
{
    public static string ToTitleCase(this string text) =>
        string.Join(' ', text.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries)
                             .Select(w => char.ToUpper(w[0]) + w[1..].ToLower()));

    public static bool IsPalindrome(this string s) =>
        s.SequenceEqual(s.Reverse());

    public static bool IsEven(this int n) => n % 2 == 0;
}
Output
The Hobbit
True
True

title.ToTitleCase() is just a nicer way of writing StringExtensions.ToTitleCase(title) — the compiler rewrites one into the other. The extension can only use the type's public members; it doesn't get special access to its internals.

Extending interfaces

Extending an interface adds the method to every type that implements it. This is exactly how LINQ works: its methods extend IEnumerable<T>, so they work on arrays, lists, sets, dictionaries and your own collections:

C#
int[] scores = [72, 95, 88, 61, 79];
List<string> names = ["Zoe", "Adam", "Mia"];

Console.WriteLine(string.Join(", ", scores.TopN(2)));
Console.WriteLine(string.Join(", ", names.TopN(1)));
Console.WriteLine(scores.ToBulletList());

static class SequenceExtensions
{
    public static IEnumerable<T> TopN<T>(this IEnumerable<T> source, int n) =>
        source.OrderDescending().Take(n);

    public static string ToBulletList<T>(this IEnumerable<T> source) =>
        string.Join("\n", source.Select(x => $"• {x}"));
}
Output
95, 88
Zoe
• 72
• 95
• 88
• 61
• 79

Extension blocks (C# 14)

C# 14 introduces a new syntax: an extension block inside a static class declares a receiver — the type being extended and a name for it — and then ordinary-looking members. Besides methods, blocks can contain extension properties:

C#
string empty = "   ";
string word = "level";

Console.WriteLine(empty.IsBlank);
Console.WriteLine(word.IsBlank);
Console.WriteLine(word.FirstChar);
Console.WriteLine(word.Shout());

static class TextExtensions
{
    extension(string s)
    {
        public bool IsBlank => string.IsNullOrWhiteSpace(s);
        public char FirstChar => s.Length > 0 ? s[0] : '\0';
        public string Shout() => s.ToUpper() + "!";
    }
}
Output
True
False
l
LEVEL!

All the members in the block share the receiver s, so you don't repeat this string s on every method. Existing extension methods keep working; the two styles can be mixed freely, and switching between them doesn't affect callers.

Generic extension blocks

Extension blocks can be generic. Here every sequence gains an IsEmpty property and a Second method:

C#
List<int> none = [];
string[] colours = ["red", "green", "blue"];

Console.WriteLine(none.IsEmpty);
Console.WriteLine(colours.IsEmpty);
Console.WriteLine(colours.Second());

static class EnumerableExtensions
{
    extension<T>(IEnumerable<T> source)
    {
        public bool IsEmpty => !source.Any();
        public T Second() => source.Skip(1).First();
    }
}
Output
True
False
green

Static extension members

A block whose receiver has no name — extension(Type) — declares static extension members, which are called on the type itself, like built-in static methods:

C#
Console.WriteLine(TimeSpan.FromWorkDays(3));
Console.WriteLine(string.Lorem(4));

static class TypeExtensions
{
    extension(TimeSpan)
    {
        public static TimeSpan FromWorkDays(int days) => TimeSpan.FromHours(days * 8);
    }

    extension(string)
    {
        public static string Lorem(int words) =>
            string.Join(' ', "lorem ipsum dolor sit amet consectetur".Split(' ').Take(words));
    }
}
Output
1.00:00:00
lorem ipsum dolor sit

Rules and limitations

  • Extension members must live in a non-generic, non-nested static class.
  • They need the class's namespace to be imported (using) to be visible — which is why using System.Linq; "turns on" LINQ.
  • If the type already has a member with the same signature, the real member always wins; your extension is silently ignored.
  • Extensions can't add state: there's no extension field, so extension properties must be computed from what the type already exposes.
  • Calling an extension method on a null reference doesn't throw by itself — the method receives null and must handle it.
C#
string? missing = null;
Console.WriteLine(missing.OrDefault("(none)"));

static class NullExtensions
{
    public static string OrDefault(this string? s, string fallback) =>
        string.IsNullOrEmpty(s) ? fallback : s;
}
Output
(none)

When to use extensions

Extensions are great for small, general-purpose helpers on types you don't control, and for adding convenience methods to interfaces. Don't use them to work around encapsulation in your own classes — if you own the type and the behaviour belongs to it, add a regular member instead. And keep them discoverable: group them in classes named after what they extend (StringExtensions, DateOnlyExtensions).

Try it yourself

Add a Truncate method and a WordCount property to string using an extension block.

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
11
Extension members let yo…
short