Partilhar via


Declaração de extensão (Referência C#)

A partir do C# 14, declarações não genéricas static class de nível superior podem usar extension contêineres para declarar membros de extensão. Os membros de extensão são métodos ou propriedades e podem surgir como membros estáticos ou de instância. Versões anteriores do C# habilitam métodos de extensão adicionando this como um modificador ao primeiro parâmetro de um método estático declarado em uma classe estática não genérica de nível superior.

O extension bloco especifica o tipo e o recetor para os membros da extensão. Você pode declarar métodos e propriedades dentro da extension declaração. O exemplo a seguir declara um único bloco de extensão que define um método de extensão de instância e uma propriedade de instância.

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

O extension define o recetor: sequence, que é um IEnumerable<int>. O tipo de recetor pode ser não genérico, um genérico aberto ou um tipo genérico fechado. O nome sequence está no escopo em cada membro de instância declarado nessa extensão. O método de extensão e a propriedade acessam o sequence.

Qualquer um dos membros da extensão pode ser acessado como se fossem membros do tipo recetor:

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

var median = numbers.Median;

Você pode declarar qualquer número de membros em um único contêiner, desde que eles compartilhem o mesmo recetor. Você também pode declarar tantos blocos de extensão em uma única classe. Extensões diferentes não precisam declarar o mesmo tipo ou nome de recetor. O parâmetro de extensão não precisa incluir o nome do parâmetro se os únicos membros forem 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>();
}

As extensões estáticas podem ser chamadas como se fossem membros estáticos do tipo recetor:

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

Importante

Uma extensão não introduz um escopo para declarações de membros. Todos os membros declarados em uma única classe, mesmo que em várias extensões, devem ter assinaturas exclusivas. A assinatura gerada inclui o tipo de recetor no seu nome para membros estáticos e o parâmetro de recetor para membros de instância de extensão.

O exemplo a seguir mostra um método de extensão usando o 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;
    }
}

O Add método pode ser chamado de qualquer outro método como se fosse um membro da IEnumerable<int> interface:

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

Ambas as formas de métodos de extensão geram a mesma linguagem intermediária (IL). Os chamadores não podem fazer distinção entre eles. Na verdade, pode-se converter métodos de extensão existentes para a sintaxe do novo membro sem provocar uma mudança disruptiva. Os formatos são binários e compatíveis com fontes.

Blocos de extensão genéricos

Onde você especificar os parâmetros de tipo para um membro de extensão declarado em um bloco de extensão depende de onde esse parâmetro de tipo é necessário:

  • Você adiciona o parâmetro "tipo" à declaração extension quando o parâmetro "tipo" é usado no recetor.
  • Você adiciona o parâmetro de tipo à declaração de membro quando o tipo é distinto de qualquer parâmetro de tipo especificado no receptor.
  • Não é possível especificar o mesmo parâmetro type em ambos os locais.

O exemplo a seguir mostra um bloco de extensão para IEnumerable<T> onde dois dos membros da extensão exigem um 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;
            }
        }
    }
}

Os membros Append e Prepend especificam o parâmetro de tipo extra para a conversão. Nenhum dos membros repete o parâmetro tipo para o recetor.

As declarações de método de extensão equivalente demonstram como esses parâmetros de tipo são codificados:

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

Ver também

Especificação da linguagem C#

Para obter mais informações, consulte a Especificação da Linguagem C# . A especificação da linguagem é a fonte definitiva para a sintaxe e o uso do C#.