# Extension Members in C# 14

In this post, I explore C# 14's [extension members](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#extension-members) feature and how it is compiled into [IL](https://learn.microsoft.com/en-us/dotnet/standard/managed-execution-process) code.

## From Extension Methods to Extension Members

Previous versions of C# allowed developers to declare extension methods. The code snippets in this post are for demonstration purposes only and should not be used directly in production applications:

```csharp
string value = "ThisIsPascalCased";
Console.WriteLine(value.ToCamel());

public static class MyExtensions
{
    public static string ToCamel(this string str) => str.Length switch
    {
        0 => string.Empty,
        1 => str.ToLowerInvariant(),
        _ => new([char.ToLower(str[0]), .. str[1..]])
    };
}
```

The `ToCamel` method takes a PascalCased (upper camel cased) `string` and converts it to [camel cased](https://en.wikipedia.org/wiki/Camel_case) by lowering the case of the first character. It is also an extension method: it is declared in a static class, as a static method, and with the `this` keyword indicating the extended type. Extension methods have been around for a long time, but they had some limitations: no support for static methods (methods that extend a type's static members) or other extension members, such as properties or events.

Extension methods are widely used across applications and frameworks. For example, LINQ uses them to extend enumerables and collections.

Extension methods are compiled as regular `static` methods decorated with the `ExtensionAttribute`. For example, the **ToCamel** method compiles to:

```IL
.method public hidebysig static // 👈 static method
	string ToCamel (
		string str
	) cil managed 
{
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
		01 00 01 00 00
	)
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( // 👈 attribute
		01 00 00 00
	)
```

> In this blog post, I used ildasm and [ILSpy](https://github.com/icsharpcode/ILSpy) to disassemble the JIT code.

Since extension methods are compiled as static methods, they can also be invoked as static methods:

```csharp
Console.WriteLine(MyExtensions.ToCamel("ThisIsPascalCased"));
```

An interesting sidenote: extension methods allow partial method declarations, but extension members do not:

```csharp
public static partial class MyExtensions
{
    public static partial string ToCamel(this string str);
}
```

## C# 14 Extension Members

C# 14 extends the capabilities of extension methods (pun intended) with a new feature: [extension members](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods). Extension members allow defining properties, static methods (here, static means a `static` member of the extended type), and static operators. These use cases are well-defined in the official documentation, hence this blog post focuses on how extension members are compiled into IL code.

The example above can be rewritten using the extension members syntax:

```csharp
public static class MyExtensionMembers
{
    extension(string str)
    {
        public string ToCamelExtensionMember() => str.Length switch
        {
            0 => string.Empty,
            1 => str.ToLowerInvariant(),
            _ => new([char.ToLower(str[0]), .. str[1..]])
        };
    }
}
```

The extended type (and a parameter name) is moved to the declaration of the extension block. This `ToCamelExtensionMember` method is no longer declared as static, and it drops the parameter with the `this` keyword. However, it can reference the `string str` parameter in a way that *feels like* a readonly field. This extension member can be used as a method on an instance of the extended type or as a static method of the enclosing static type:

```csharp
string value = "ThisIsPascalCased";
Console.WriteLine(value.ToCamelExtensionMember());
Console.WriteLine(MyExtensionMembers.ToCamelExtensionMember(value));
```

Let's extend `MyExtensionMembers` with extension properties and `static` extension methods:

```csharp
extension(string str)
{
    // ...
    public static string ToCamelStatic(string input) => input.ToCamelExtensionMember();
    public bool IsCamelStart => char.IsLower(str[0]);
}

// Usage examples 👇
Console.WriteLine(string.ToCamelStatic(value));
Console.WriteLine(value.IsCamelStart);
```

Static extension methods are invoked using static method syntax (e.g., `string.ToCamelStatic(value)`). Extension properties can have getters and setters, but they can only access the public interface of the extended type or the static context. While the syntax and usage feel natural, the generated IL looks significantly different from extension methods. Let's examine the generated IL next (explanations are added as code comments).

The compiler still generates the static methods in the `MyExtensionMembers` class:

```IL
	.method public hidebysig static 
		string ToCamelExtensionMember (
			string str
		) cil managed 
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = (
			01 00 00 00
		)
		// IL code of the method omitted for brevity
	} 

	.method public hidebysig static 
		string ToCamelStatic (
			string input
		) cil managed 
	{
        // IL code of the method omitted for brevity
	}

	.method public hidebysig static 
		bool get_IsCamelStart (
			string str
		) cil managed 
	{
        // IL code of the method omitted for brevity
	}
```

These methods contain the compiled IL for method bodies, making extension members backward compatible with extension methods. Existing compilers and tooling can handle extension members, though they may not display them correctly. Note that the `IsCamelStart` property's getter is compiled to a method named `get_IsCamelStart`, which can be invoked from the static enclosing type's scope as:

```csharp
Console.WriteLine(MyExtensionMembers.get_IsCamelStart(value));
```

The enclosing `MyExtensionMembers` now has a new nested type:

```IL
.class nested public auto ansi sealed specialname '<G>$34505F560D9EACF86A87F3ED1F85E448'
    extends [System.Runtime]System.Object
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = (
        01 00 00 00
    )
    // 👇 Implementation of an inner-inner nested Type discussed later
    .class nested public auto ansi abstract sealed specialname '<M>$ECEADA5840DE47E35C0C15BC038C831E' // ...

    // ⚠️ Notice ToCamelExtensionMember is an instance member.
    .method public hidebysig instance string ToCamelExtensionMember() cil managed 
    {
        // 👇 ExtensionMarker Attribute referencing the inner-inner nested type
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = (...)
        .maxstack 8
        
        // 👇 All member bodies throw
        IL_0000: newobj instance void [System.Runtime]System.NotSupportedException::.ctor()
        IL_0005: throw
    }

    // 👉 Notice ToCamelStatic is a static member on this type.
    .method public hidebysig static 
        string ToCamelStatic (
            string input
        ) cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = (...)
        .maxstack 8
        IL_0000: newobj instance void [System.Runtime]System.NotSupportedException::.ctor()
        IL_0005: throw
    }

    .method public hidebysig specialname 
        instance bool get_IsCamelStart () cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = (...)
        .maxstack 8

        IL_0000: newobj instance void [System.Runtime]System.NotSupportedException::.ctor()
        IL_0005: throw
    }

    .property instance bool IsCamelStart()
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = (...)
        .get instance bool MyExtensionMembers/'<G>$34505F560D9EACF86A87F3ED1F85E448'::get_IsCamelStart()
    }
}
```

This generated class with a special (hidden) type name `<G>$34505F560D9EACF86A87F3ED1F85E448` looks like a regular type with instance methods, static methods, and properties. However, it only defines the signatures of the methods, all implementations throw a `NotSupportedException`. Remember, the actual implementations are defined in the backward compatible way. There is no reason to duplicate the same IL.

The `ToCamelExtensionMember` is an instance method. But how can the compiler tell on what type is extended? The `<G>$34505F560D9EACF86A87F3ED1F85E448` special type has no reference to the `string str` declaration on the extension block. This is where another nested public type helps: all members of the current type are attributed with `ExtensionMarkerAttribute` pointing to yet another inner nested type. This nested type has a single method with a no-op implementation:

```IL
.class nested public auto ansi abstract sealed specialname '<M>$ECEADA5840DE47E35C0C15BC038C831E'
    extends [System.Runtime]System.Object
{
    .method public hidebysig specialname static 
        void '<Extension>$' (
            string str
        ) cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (01 00 00 00)
        .maxstack 8
        IL_0000: ret
    }
}
```

This type has a method with the parameter `string str` matching the extension block declaration. Compilers, tooling, and the runtime can use this information to infer the extended type.

## Interesting Aspects

### Compile time binding

The documentation clarifies the primary use-case for extension methods:

> Extension members are preferable when the original source isn't under your control, when a derived object is inappropriate or impossible, or when the functionality has limited scope.

Let's use custom type with an extension method through an example. We can assume that `MyType` is defined in a 3rd party nuget package, and the extension member is defined in a local codebase referencing the nuget package:

```csharp
public record class MyType(int Value);
//...

extension(MyType mt)
{
    public int InflatedValue => mt.Value * 2;
}
```

The **InflatedValue** property returns the double of the Value property from `MyType`.


```csharp
var myType = new MyType(1);
Console.WriteLine(myType.InflatedValue); // 👈 prints 2
```

Now the owner of the nuget package introduces `InflatedValue` property directly on `MyType`:

```csharp
public record class MyType(int Value)
{
    public int InflatedValue => Value * 3;
}
```

When the consuming application updates to the new NuGet package version, the previous example will alter the behavior:

```csharp
var myType = new MyType(1);
Console.WriteLine(myType.InflatedValue); // 👈 prints 3
```

This is because the compiler prefers to bind to the `InflatedValue` property on the defining type itself rather than the extension property.

### Extension Members with ref modifier

Extension members allow *ref* and *in* modifiers (not *out*):

```csharp
extension(ref int value)
{
    public void Calculate() => value = 10;
}

//...
int num = 4;
ref int numRef = ref num;
numRef.Calculate();
Console.WriteLine(numRef); // 👈 prints 10
```

## Conclusion

Extension members in C# 14 provide a powerful and natural way to extend types with properties, static methods, and operators without modifying their original definitions. They maintain backward compatibility with extension methods while opening new possibilities for developers. However, understanding compile time binding semantics and potential member shadowing is essential for using this feature effectively. With careful consideration of its capabilities and best practices, extension members can significantly enhance modern C# development.