C# 14 introduces a powerful enhancement to the concept of extension methods: extension members. This feature allows you to extend not just instance methods, but also properties, indexers, and even static members of a type — with cleaner, more expressive syntax.
In this post, we’ll explore:
- What extension members are
- How to declare and use them
- The difference between instance and static extension members
- A real-world example
- When and why to use them
Motivation — From Extension Methods to Extension Members
Before C# 14, we could only extend types with methods using static classes:
public static class StringExtensions
{
public static bool IsNullOrWhiteSpace(this string str)
=> string.IsNullOrWhiteSpace(str);
}
Useful? Yes. But limiting — no properties, no indexers, no static-style access. C# 14 changes that with a new syntax: extension<>()
.
Syntax Breakdown
Here’s the new structure in C# 14:
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source)
{
// Instance-like extension members here
}
extension<TSource>(IEnumerable<TSource>)
{
// Static-like extension members here
}
}
Now let’s walk through a complete example 👇
Example – Extending IEnumerable with Powerful Additions
public static class Enumerable
{
// 🔹 Instance-style extension members
extension<TSource>(IEnumerable<TSource> source)
{
// ✅ Extension Property
public bool IsEmpty => !source.Any();
// ✅ Extension Indexer
public TSource this[int index] => source.Skip(index).First();
// ✅ Extension Method
public IEnumerable<TSource> Filter(Func<TSource, bool> predicate)
=> source.Where(predicate);
}
// 🔸 Static-style extension members
extension<TSource>(IEnumerable<TSource>)
{
// ✅ Static Extension Method
public static IEnumerable<TSource> Combine(IEnumerable<TSource> first, IEnumerable<TSource> second)
=> first.Concat(second);
// ✅ Static Extension Property
public static IEnumerable<TSource> Identity => System.Linq.Enumerable.Empty<TSource>();
}
}
How to Use It
var numbers = new[] { 10, 20, 30 };
// Instance-style usage
bool empty = numbers.IsEmpty; // 🔹 false
int second = numbers[1]; // 🔹 20
var evens = numbers.Filter(n => n % 2 == 0); // 🔹 {10, 20, 30}
// Static-style usage
var zeros = Enumerable<int>.Identity; // 🔸 Empty sequence of int
var combined = Enumerable<int>.Combine(new[] { 1 }, new[] { 2 }); // 🔸 {1, 2}
Behind the Scenes – What’s Really Happening?
C# 14 internally rewrites these extension
blocks into static methods behind the scenes, just like regular extension methods, but the syntax sugar allows:
- Properties with
=>
- Indexers using
[index]
- Static-style access without utility class noise
- Stronger IDE support and IntelliSense
Best Use Cases
Scenario | Why Extension Members Help |
---|---|
Fluent LINQ-style APIs | Add expressive filters, transforms, summaries |
DSL for collections | Indexers, state introspection (IsEmpty , IsSorted , etc.) |
Static factories/utilities | Return Empty , Singleton , or default prebuilt objects |
Domain-driven design patterns | Model behaviors tied to interfaces without modifying them |
Notes & Tips
- Only available in C# 14+ with .NET 10 SDK or higher.
- Must be placed inside a static class.
- Static extension members must use the type receiver, not an instance.
- IntelliSense automatically differentiates between static and instance extension members.
Learn More
Final Thoughts
C# 14’s extension members open the door to expressive, modular, and scalable designs. Whether you're building LINQ-like utilities, extending existing APIs, or designing fluent interfaces, this feature makes your code more elegant and natural.
Start refactoring your extension utils into structured extension blocks today!
✍️ _Written by: [Cristian Sifuentes] - C# Architect | .NET Engineer |
📩 Got questions or ideas? Leave a comment or DM!
Top comments (0)