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:
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; }
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:
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}")); }
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:
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() + "!"; } }
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:
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(); } }
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:
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)); } }
1.00:00:00 lorem ipsum dolor sit
Rules and limitations
- Extension members must live in a non-generic, non-nested
staticclass. - They need the class's namespace to be imported (
using) to be visible — which is whyusing 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
nulland must handle it.
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; }
(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.
11 Extension members let yo… short