Compartir a través de


Declaración de extensión (Referencia de C#)

A partir de C# 14, las declaraciones no genéricas de nivel superior static class pueden utilizar contenedores extension para declarar miembros de extensión. Los miembros de extensión son métodos o propiedades y pueden parecer ser miembros estáticos o de instancia. Las versiones anteriores de C# habilitan los métodos de extensión agregando this como modificador al primer parámetro de un método estático declarado en una clase estática de nivel superior y no genérico.

El extension bloque especifica el tipo y el destinatario de los miembros de la extensión. Puede declarar métodos y propiedades dentro de la extension declaración. En el ejemplo siguiente se declara un único bloque de extensión que define un método de extensión de instancia y una propiedad de instancia.

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();
    }
}

extension define el receptor: sequence, que es un IEnumerable<int>. El tipo de receptor puede ser no genérico, un genérico abierto o un tipo genérico cerrado. El nombre sequence está en el ámbito de cada miembro de instancia declarado en esa extensión. Tanto el método de extensión como la propiedad acceden a sequence.

Se puede tener acceso a cualquiera de los miembros de la extensión como si fueran miembros del tipo de receptor:

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

var median = numbers.Median;

Puede declarar cualquier número de miembros en un único contenedor, siempre que compartan el mismo receptor. También puede declarar tantos bloques de extensión en una sola clase. No es necesario que diferentes extensiones declaren el mismo tipo o nombre del receptor. El parámetro de extensión no necesita incluir el nombre del parámetro si los únicos miembros son estáticos:

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>();
}

Se puede llamar a las extensiones estáticas como si fuesen miembros estáticos del tipo receptor.

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

Importante

Una extensión no introduce un ámbito para las declaraciones de miembro. Todos los miembros declarados en una sola clase, incluso si en varias extensiones, deben tener firmas únicas. La firma generada incluye el tipo de receptor en su nombre para los miembros estáticos y el parámetro receptor para los miembros de instancia de extensión.

En el ejemplo siguiente se muestra un método de extensión mediante el this modificador :

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

Se puede llamar al Add método desde cualquier otro método como si fuera miembro de la IEnumerable<int> interfaz:

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

Ambas formas de métodos de extensión generan el mismo lenguaje intermedio (IL). Los autores de llamadas no pueden distinguir entre ellos. De hecho, puede transformar los métodos de extensión existentes en la nueva sintaxis de miembro sin causar un cambio importante. Los formatos son binarios y compatibles con el origen.

Bloques de extensión genéricos

Cuando especifique los parámetros de tipo para un miembro de extensión declarado en un bloque de extensión depende de dónde se requiera ese parámetro de tipo:

  • Agregue el parámetro tipo a la declaración extension cuando se utilice el parámetro tipo en el receptor.
  • Agregue el parámetro de tipo a la declaración de miembro cuando el tipo sea distinto de cualquier parámetro de tipo especificado en el receptor.
  • No se puede especificar el mismo parámetro de tipo en ambas ubicaciones.

En el ejemplo siguiente se muestra un bloque de extensión para IEnumerable<T>, donde dos de los miembros de extensión requieren un segundo parámetro de tipo:

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;
            }
        }
    }
}

Los miembros Append y Prepend especifican el parámetro de tipo adicional para la conversión. Ninguno de los miembros repite el parámetro de tipo para el receptor.

Las declaraciones de métodos de extensión equivalentes muestran cómo se codifican esos parámetros de tipo:

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;
        }
    }
}

Consulte también

Especificación del lenguaje C#

Para obtener más información, consulte la Especificación del lenguaje C#. La especificación del lenguaje es el origen definitivo de la sintaxis y el uso de C#.