Introduction
Starting with .NET 10 Preview 3, C# 14 introduces a new feature called extension members. Although this feature is still under development and doesn’t yet support all the member types it eventually will, its goal is to expand on the concept of extension methods. Not only does it aim to replace traditional extension methods, but it also introduces the ability to add other kinds of extension members to a type.
Traditional extension methods allow developers to add methods to existing types, giving the appearance that they are part of the type itself. This new feature retains that familiar syntax but expands its capabilities by allowing developers to extend types with more than just instance methods. As of the time of writing, extension members support instance and static methods, as well as properties, with plans to include additional member types in the future.
New Extension Syntax
To declare extension members, C# now introduces a new extension
keyword. While the syntax may initially seem less concise than the traditional approach, it offers greater flexibility and improved code organization.
The extension
keyword is used within a static class and has to contain in parenthesis before the beginning of the extension block, the type we wish to extend. Here’s an example comparing the old and new syntax for declaring an extension method:
Suppose we have the following Foo
class:
public class Foo
{
public int Counter { get; set; }
public void DoSomething() => Console.WriteLine("Doing something");
}
Using the traditional approach, we would create an extension method like this:
public static class FooExtensions
{
public static void DoSomethingExtended(this Foo foo) => Console.WriteLine("Doing something Extended");
}
With the new syntax, the same method would be written as:
public static class MyExtensions
{
extension(Foo foo)
{
public void DoSomethingExtended() => Console.WriteLine("Doing something Extended");
}
}
Here, the this Foo foo
syntax is replaced by the extension(Foo foo)
block. While this introduces a new level of indentation, it offers advantages in flexibility and organization.
One key benefit is support for members, like properties, that previously had no mechanism to specify the extended type. While this could theoretically be achieved by including the extension
keyword in a class declaration and passing the extended type as a parameter, the new syntax allows multiple types to be extended within a single class. For example
public static class MyExtensions
{
extension(Foo foo)
{
public void DoSomethingExtended() => Console.WriteLine("Doing something Extended");
}
extension(Bar bar)
{
public void DoSomethingExtended() => Console.WriteLine("Doing something Extended for Bar instances");
}
}
Instance and Static Methods and Properties
As of this writing, extension members support both instance and static methods, as well as properties. Here’s an example of an instance property for Foo
:
public static class MyExtensions
{
extension(Foo foo)
{
public bool IsCounterNegativeOrZero => foo.Counter <= 0;
}
}
And here are instance methods that utilize existing members of Foo
:
public static class MyExtensions
{
extension(Foo foo)
{
public void DoSomethingMultiple(int repeats)
{
for (int i = 0; i < repeats; i++)
{
foo.DoSomething();
}
}
public int IncreaseCounter(int amount) => foo.Counter++;
}
}
For static extension members, you don’t need to specify an instance variable, just the type itself:
public static class MyExtensions
{
extension(Foo)
{
public static void WriteType() => Console.WriteLine(nameof(Foo));
public static int ATypeNumber => 999;
}
}
These members can then be used like so:
Foo.WriteType();
Console.WriteLine(Foo.ATypeNumber);
Extension methods can also be generic. For example, suppose we have this class:
public class Bar<T>
{
public string GetName() => nameof(T);
}
We can define an extension member for Bar<T>
that is only available when T
is constrained to Foo
:
public static class MyExtensions
{
extension<T>(Bar<T> bar) where T: Foo, new ()
{
public void CreateFooToDoSomething()
{
var temp = new T();
temp.DoSomething();
Console.WriteLine(temp.IsCounterNegativeOrZero);
}
}
}
Now, whenever the T
is of type Foo
, we can use the CreateFooToDoSomething
method on instances of Bar
to create a new instance of Foo
and then call its DoSomething
method, or even its IsCounterNegativeOrZero
extension property.
New generics can also be added, for example:
public static class MyExtensions
{
extension(Foo)
{
public static T Squared<T>(T param) where T: INumber<T> => param * param;
}
}
Now the following is a valid static method for Foo
:
Console.WriteLine(Foo.Squared(2));
Auto Properties
You may have noticed that extension properties so far don’t include auto properties. This is because auto properties require a backing field, which implies modifying the internal state of the type, something not currently possible, as extension fields are not supported.
According to the Microsoft team behind extension members, support for static and constant fields may be implemented in the near future, but until then, here’s how you can create a static auto property using a manual backing field:
Suppose we have the following class:
public class FooBar
{
public static int AField = 69;
}
We can extend it with what will look like a static auto property like this:
public static class FooBarExtensions
{
private static int aBackingField = 10;
extension(FooBar)
{
public static int AProperty
{
get => aBackingField;
set => aBackingField = value;
}
}
}
We introduced a static backing field, inside the static extension class. This makes the following code legal:
Console.WriteLine(FooBar.AField);
Console.WriteLine(FooBar.AProperty);
FooBar.AProperty = 42;
Console.WriteLine(FooBar.AProperty);
Conclusion
This has been a preview of the new extension members feature in C# 14, which is still under active development at the time of writing. Currently, only static and instance methods, as well as static and instance properties that do not introduce new state, are supported. However, support for static and constant fields is being considered for the near future.
Beyond enabling extensions for more than just instance methods, the introduction of the new extension keyword brings a change in syntax. While this new syntax may initially seem a bit unusual, primarily due to the added level of indentation from the new code block, it was intentionally designed this way. The goal is to allow extension members whose syntax wouldn’t otherwise support specifying the type they extend, as well as to enable a single class to contain extension members for multiple types.
Thank you for reading, and if you have any questions or comments you can use the comments section, or contact me directly via the contact form or by email. Also, if you don’t want to miss any of the new blog posts, you can subscribe to my newsletter or the RSS feed.