C# 14: Extension Members
.NET 10 and C# 14 will be released in November 2025. So it’s time to look at some of the planned new, cool features with a series of blog posts. Let’s start with Extension Members.
Before we start looking at extension members, let’s clarify how Extension Methods work.
Extension Methods Introduced in C# 3
In 2007, with the release of .NET Framework 3.5, Microsoft introduced C# 3. A part of C# 3 were Extension Methods, and you can use them today exactly in the way as they have been introduced back in 2007. So, let’s take a look at them. Let’s take this simple Developer class below:
public class Developer
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
Now let’s say that you need to get the full name for a developer at several places in your code, but you cannot change the code of this class, as it lives for example in a library that you don’t own. This means you neither can add a FullName property to the Developer class nor a GetFullName method. But you can create a GetFullName extension method that extends the Developer type.
The code block below shows a GetFullName extension method. The only difference to a normal static method is that the first parameter has the this keyword in front of it. This makes this method not only usable as normal static method, but also as an extension method directly on a Developer instance.
public static class DeveloperExtensions
{
public static string GetFullName(this Developer dev)
{
return $"{dev.FirstName} {dev.LastName}";
}
}
You can call the GetFullName method like a normal static method like below:
var dev = new Developer
{
FirstName = "Thomas",
LastName = "Huber"
};
// Call the GetFullName method like a normal static method
var fullName = DeveloperExtensions.GetFullName(dev);
Or you can call the GetFullName method as an extension method directly on the Developer instance as you can see it in the next snippet. Behind the scenes, the C# compiler does exactly what you can see above – it calls the static GetFullName method and passes in the Developer instance:
var dev = new Developer
{
FirstName = "Thomas",
LastName = "Huber"
};
// Call the GetFullName method as an extension method
var fullName = dev.GetFullName();
Do you want to play around with this extension method in a Console app? Here’s the code that you can paste into the Program.cs file:
var dev = new Developer
{
FirstName = "Thomas",
LastName = "Huber"
};
var fullName = dev.GetFullName();
Console.WriteLine(fullName);
public class Developer
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
public static class DeveloperExtensions
{
public static string GetFullName(this Developer dev)
{
return $"{dev.FirstName} {dev.LastName}";
}
}
Add a Using Directive for the Extension Method
To use an extension method like the GetFullName extension method, the class containing the extension method – in our case the DeveloperExtensions class – must be known in the file where you want to use the extension method. In the previous section it worked out-of-the-box, as I put everything into a single file, the Developer class, the DeveloperExtensions class and the code that uses both. But let’s look at the logic when you have different namespaces. Let’s put our classes into the namespaces TCH.Models and TCH.Extensions:
namespace TCH.Models
{
public class Developer
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
}
namespace TCH.Extensions
{
using TCH.Models;
public static class DeveloperExtensions
{
public static string GetFullName(this Developer dev)
{
return $"{dev.FirstName} {dev.LastName}";
}
}
}
Now, when you want to use the extension method, you have to add a using directive for the TCH.Extensions namespace like below. Without that using directive, you get a compile error that will tell you that the type Developer does not contain a definition for GetFullName:
using TCH.Models;
using TCH.Extensions; // Without this, the GetFullName extension method is not available
var dev = new Developer
{
FirstName = "Thomas",
LastName = "Huber"
};
var fullName = dev.GetFullName();
Also here, you can paste everything below into the Program.cs file of a Console app to play with it:
using TCH.Extensions;
using TCH.Models;
var dev = new Developer
{
FirstName = "Thomas",
LastName = "Huber"
};
var fullName = dev.GetFullName();
Console.WriteLine(fullName);
namespace TCH.Models
{
public class Developer
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
}
namespace TCH.Extensions
{
using TCH.Models;
public static class DeveloperExtensions
{
public static string GetFullName(this Developer dev)
{
return $"{dev.FirstName} {dev.LastName}";
}
}
}
For simplicity reasons, I switch back to code without namespaces for this blog post. But what you just learned here about the required using directive is not only valid for extension methods, but also for the extension members we look at in a minute. Before this, let me explain the syntax issue with Extension Methods.
The Syntax Issue with Extension Methods
Don’t worry, you can continue to write extension methods as you always did and as you have seen it so far in this blog post, there is no problem with that. But when you think about it from a C# language designer perspective, there is an issue when you want to introduce extension members like extension properties. Take a look at the extension method that we used in this blog post:
public static class DeveloperExtensions
{
public static string GetFullName(this Developer dev)
{
return $"{dev.FirstName} {dev.LastName}";
}
}
What could be the issue with this syntax when you want to introduce extension properties?
The problem is that the Developer type that this method extends is defined as a method parameter. And properties don’t have parameters. So, the type that an extension member like an extension property extends cannot be specified as a method parameter. That’s the reason why Microsoft introduced a new syntax with C# 14 that allows you to specify that extended type for any extension member, no matter if it’s an extension method or an extension property. This means that you can also re-write your existing extension methods like the one above with that new syntax (The old syntax from above continues to work). So, let’s start with that.
The C# 14 Syntax to write Extension Methods
When you play with the preview bits of .NET 10, you’re using by default C# 13. To use C# 14, set the LangVersion element in your .csproj file to Preview. When .NET 10 is out of preview, C# 14 will be the default and you can use the new syntax out-of-the-box.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>Preview</LangVersion>
</PropertyGroup>
</Project>
Now, let’s write this classic extension method below with the newer C# 14 syntax:
public static class DeveloperExtensions
{
public static string GetFullName(this Developer dev)
{
return $"{dev.FirstName} {dev.LastName}";
}
}
To do this, you create a new block with the extension keyword like you see it below. You put that new block into the DeveloperExtensions class and around the existing GetFullName method. With the extension block, you specify the Developer type that is extended and you receive the extended instance in a dev variable. This looks exactly like a normal method parameter. The inner GetFullName method is not static anymore, also the Developer method parameter was removed, as that one is now received from the extension block. Internally, the GetFullName method stayed exactly the same. But it now uses the dev parameter specified with the extension block.
public static class DeveloperExtensions
{
extension(Developer dev)
{
public string GetFullName()
{
return $"{dev.FirstName} {dev.LastName}";
}
}
}
From a usage perspective – how you call the extension method – everything stays the same. So, the new syntax is just a new way to implement an extension method. And now the problem that the extended type is specified as a method parameter is gone. So, extension properties can be created.
Create an Extension Property
With the new syntax, you can define extension properties like in the next snippet. There you can see a FullName extension property. This is super straight-forward and there’s nothing special. Once you understood the extension block, it’s absolutely brilliant and easy to use.
public static class DeveloperExtensions
{
extension(Developer dev)
{
public string FullName
{
get => $"{dev.FirstName} {dev.LastName}";
}
}
}
This means that you can now use on a Developer instance the FullName extension property like you would expect:
var dev = new Developer
{
FirstName = "Thomas",
LastName = "Huber"
};
var fullName = dev.FullName;
Of course, the extension property is only available if you have the corresponding using directive in your C# file. This stayed the same as before with extension methods.
Extend the Type (Static Extensions)
With the new syntax, you can also extend a type. You simply do this by defining static members in your extension block. Take a look at the static GetThomasDev method below:
public static class DeveloperExtensions
{
extension(Developer dev)
{
public string FullName
{
get => $"{dev.FirstName} {dev.LastName}";
}
public static Developer GetThomasDev()
{
return new Developer
{
FirstName = "Thomas",
LastName = "Huber"
};
}
}
}
Now you can use both – the GetThomasDev method and the FullName property – like this:
// Use GetThomasDev extension method
var dev = Developer.GetThomasDev();
// Use FullName extension property
var fullName = dev.FullName;
Below is the full code snippet that you can paste into the Program.cs file of a Console app (don’t forget to set the LangVersion element in your .csproj file to Preview if you’re working with the .NET 10 preview bits):
var dev = Developer.GetThomasDev();
var fullName = dev.FullName;
Console.WriteLine(fullName);
public class Developer
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
public static class DeveloperExtensions
{
extension(Developer dev)
{
public string FullName
{
get => $"{dev.FirstName} {dev.LastName}";
}
public static Developer GetThomasDev()
{
return new Developer
{
FirstName = "Thomas",
LastName = "Huber"
};
}
}
}
Good to Know
You can also mix the old and the new way if you want. And you can create multiple extension blocks in a single class. Below you see an example. Note that on the second extension block the dev parameter is not specified, just the Developer type. This is because there we don’t need the Developer instance, as that block defines just a static method for the Developer type.
public static class DeveloperExtensions
{
public static string GetFullName(this Developer dev)
{
return $"{dev.FirstName} {dev.LastName}";
}
extension(Developer dev)
{
public string FullName
{
get => $"{dev.FirstName} {dev.LastName}";
}
}
extension(Developer)
{
public static Developer GetThomasDev()
{
return new Developer
{
FirstName = "Thomas",
LastName = "Huber"
};
}
}
}
Of course, you can also use your extension members within your other extension members. Below you see the FullName property used in the GetFullName method:
public static class DeveloperExtensions
{
public static string GetFullName(this Developer dev)
{
return dev.FullName; // Using the FullName extension property
}
extension(Developer dev)
{
public string FullName
{
get => $"{dev.FirstName} {dev.LastName}";
}
}
}
What about Generics?
For extension methods, you quite often extend a generic class or interface like for example IEnumerable<T>. The extension block supports Generics too. Below you see an example that you can paste into the Program.cs file of a Console app. It contains a generic extension block for the type IEnumerable<T>. In the extension block, a PrintAll extension method is defined. At the top, the PrintAll extension method is called on an IEnumerable<string> instance to write the elements to the Console.
IEnumerable<string> names = ["Thomas", "Sara", "Julia"];
names.PrintAll();
public static class EnumerableExtensions
{
extension<T>(IEnumerable<T> collection)
{
public void PrintAll()
{
foreach (var item in collection)
{
Console.WriteLine(item);
}
}
}
}
Summary
With C# 14, you can not only create extension methods, but also extension properties. The new extension block makes this super simple, as it allows you to specify the extended type in a clean way. Many places in my projects come to my mind where I created an extension method that could have been an extension property. So, I will definitely use this new feature.
I probably also re-write my existing extension methods with the new syntax. Why? I noticed quite often that new developers find it strange that you define a static method that is then called like an instance method. With the new extension block, you define an instance method that is called like an instance method. So, in my opinion, from a semantic point of view, this new syntax with the extension block is cleaner than the old extension method syntax that uses a static method.
Thanks for reading and happy coding,
Thomas
Leave a Reply