Edit

Share via


Extension declaration (C# Reference)

Beginning with C# 14, top level, nongeneric static class declarations can use extension containers to declare extension members. Extension members are methods or properties and can appear to be instance or static members. Earlier versions of C# enable extension methods by adding this as a modifier to the first parameter of a static method declared in a top-level, nongeneric static class.

The extension block specifies the type and receiver for extension members. You can declare methods and properties inside the extension declaration. The following example declares a single extension block that defines an instance extension method and an instance property.

public static class NumericSequences
{
    extension(IEnumerable<int> sequence)
    {
        public IEnumerable<int> AddValue(int operand)
        {
            foreach (var item in sequence)
            {
                yield return item + operand;
            }
        }

        public int Median
        {
            get
            {

                var sortedList = sequence.OrderBy(n => n).ToList();
                int count = sortedList.Count;
                int middleIndex = count / 2;

                if (count % 2 == 0)
                {
                    // Even number of elements: average the two middle elements
                    return (sortedList[middleIndex - 1] + sortedList[middleIndex]);
                }
                else
                {
                    // Odd number of elements: return the middle element
                    return sortedList[middleIndex];
                }
            }
        }

        public int this[int index] => sequence.Skip(index).First();
    }
}

The extension defines the receiver: sequence, which is an IEnumerable<int>. The receiver type can be nongeneric, an open generic, or a closed generic type. The name sequence is in scope in every instance member declared in that extension. The extension method and property both access sequence.

Any of the extension members can be accessed as though they were members of the receiver type:

IEnumerable<int> numbers = Enumerable.Range(1, 10);
numbers = numbers.AddValue(10);

var median = numbers.Median;

You can declare any number of members in a single container, as long as they share the same receiver. You can declare as many extension blocks in a single class as well. Different extensions don't need to declare the same type or name of receiver. The extension parameter doesn't need to include the parameter name if the only members are static:

extension(IEnumerable<int>)
{
    // Method:
    public static IEnumerable<int> Generate(int low, int count, int increment)
    {
        for (int i = 0; i < count; i++)
            yield return low + (i * increment);
    }

    // Property:
    public static IEnumerable<int> Identity => Enumerable.Empty<int>();
}

Static extensions can be called as though they're static members of the receiver type:

var newSequence = IEnumerable<int>.Generate(5, 10, 2);
var identity = IEnumerable<int>.Identity;

Important

An extension doesn't introduce a scope for member declarations. All members declared in a single class, even if in multiple extensions, must have unique signatures. The generated signature includes the receiver type in its name for static members and the receiver parameter for extension instance members.

The following example shows an extension method using the this modifier:

public static class NumericSequenceExtensionMethods
{
    public static IEnumerable<int> AddValue(this IEnumerable<int> sequence, int operand)
    {
        foreach (var item in sequence)
            yield return item + operand;
    }
}

The Add method can be called from any other method as though it was a member of the IEnumerable<int> interface:

IEnumerable<int> numbers = Enumerable.Range(1, 10);
numbers = numbers.AddValue(10);

Both forms of extension methods generate the same intermediate language (IL). Callers can't make a distinction between them. In fact, you can convert existing extension methods to the new member syntax without a breaking change. The formats are both binary and source compatible.

Generic extension blocks

Where you specify the type parameters for an extension member declared in an extension block depends on where that type parameter is required:

  • You add the type parameter to the extension declaration when the type parameter is used in the receiver.
  • You add the type parameter to the member declaration when the type is distinct from any type parameter specified on the receiver.
  • You can't specify the same type parameter in both locations.

The following example shows an extension block for IEnumerable<T> where two of the extension members require a second type parameter:

public static class GenericExtensions
{
    extension<TReceiver>(IEnumerable<TReceiver> source)
    {
        public IEnumerable<TReceiver> Spread(int start, int count)
            => source.Skip(start).Take(count);

        public IEnumerable<TReceiver> Append<TArg>(IEnumerable<TArg> second, Func<TArg, TReceiver> Converter)
        {
            foreach(TReceiver item in source)
            {
                yield return item;
            }
            foreach (TArg item in second)
            {
                yield return Converter(item);
            }
        }

        public IEnumerable<TReceiver> Prepend<TArg>(IEnumerable<TArg> second, Func<TArg, TReceiver> Converter)
        {
            foreach (TArg item in second)
            {
                yield return Converter(item);
            }
            foreach (TReceiver item in source)
            {
                yield return item;
            }
        }
    }
}

The members Append and Prepend specify the extra type parameter for the conversion. None of the members repeat the type parameter for the receiver.

The equivalent extension method declarations demonstrate how those type parameters are encoded:

public static class GenericExtensions
{
    public static IEnumerable<T> Spread<T>(this IEnumerable<T> source, int start, int count)
        => source.Skip(start).Take(count);

    public static IEnumerable<T1> Append<T1, T2>(this IEnumerable<T1> source, IEnumerable<T2> second, Func<T2, T1> Converter)
    {
        foreach (T1 item in source)
        {
            yield return item;
        }
        foreach (T2 item in second)
        {
            yield return Converter(item);
        }
    }

    public static IEnumerable<T1> Prepend<T1, T2>(this IEnumerable<T1> source, IEnumerable<T2> second, Func<T2, T1> Converter)
    {
        foreach (T2 item in second)
        {
            yield return Converter(item);
        }
        foreach (T1 item in source)
        {
            yield return item;
        }
    }
}

See also

C# language specification

For more information, see the C# Language Specification. The language specification is the definitive source for C# syntax and usage.