One of the exciting additions coming with .NET 10 and C# 14 is the introduction of extension members.
This feature expands on our beloved concept of extension methods, allowing us to finally add not only methods, but also instance members and properties to existing types without modifying source code.
Traditional Extension Methods
For years, extension methods have been the standard way to add functionality to existing types without modifying them directly.
Here's an example:
public static class EnumerableExtensions
{
public static IEnumerable<int> WhereGreaterThan(this IEnumerable<int> source, int threshold)
{
return source.Where(x => x > threshold);
}
}
And now you can use them like any other method:
var numbers = new List<int> { 1, 3, 5, 7, 9 };
var greater = numbers.WhereGreaterThan(5);
Console.WriteLine(greater.Count()); // Output: 2
This approach has served developers well.
However, it comes with its own limitations:
- You can only add static methods
- You cannot add properties or instance members
New Extension Members
C# 14 introduces yet another keyword: extension.
This enables defining extension blocks that contain multiple methods or properties for the same receiver type:
public static class EnumerableExtensions
{
extension(IEnumerable<int> source)
{
public IEnumerable<int> WhereGreaterThan(int threshold)
=> source.Where(x => x > threshold);
}
}
Now, you can call this method like before:
var numbers = new List<int> { 1, 3, 5, 7, 9 };
var greater = numbers.WhereGreaterThan(5);
Console.WriteLine(greater.Count()); // Output: 2
In my opinion, this syntax is cleaner and feels like a natural extension of the type.
Under the hood, the compiler still lowers extension members to regular static methods, ensuring full backward compatibility.
You can also make your extension blocks generic and even add constraints:
public static class EnumerableExtensions
{
extension<T>(IEnumerable<T> source) where T : INumber<T>
{
public IEnumerable<T> WhereGreaterThan(T threshold)
=> source.Where(x => x > threshold);
}
}
This single block now works with int, double, decimal, or any numeric type implementing INumber<T>.
Extension Properties
Another huge improvement is the ability to define extension properties.
For example, you can now easily add an IsEmpty property to your collection:
public static class EnumerableExtensions
{
extension<T>(IEnumerable<T> source)
{
public bool IsEmpty => !source.Any();
}
}
Now you can use it just like any native property:
var numbers = new List<int> { 1, 2, 3 };
Console.WriteLine(numbers.IsEmpty); // Output: False
This is definitely a nice feature that will make our code more expressive and readable.
Conclusion
Extension members in C# 14 build on extension methods and add properties and other member kinds too.
You declare the extended type in one extension block and keep related methods and properties together. When you need generics, you write the constraint once in that block instead of repeating it everywhere.
Extension properties let helpers like IsEmpty read like normal properties when you use them.
If you want to check out examples I created, you can find the source code here:
Source CodeI hope you enjoyed it. Subscribe and get a notification when a new blog is up!
