Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
A partir do C# 14, declarações não genéricas static class de nível superior podem usar extension blocos para declarar membros de extensão. Membros de extensão são métodos ou propriedades e podem parecer membros estáticos ou de instância. Versões anteriores do C# habilitam métodos de extensão adicionando this como 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 receptor para membros da extensão. Você pode declarar métodos, propriedades ou operadores 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, uma propriedade de instância e um método de operador estático.
Observação
Todos os exemplos neste artigo incluem comentários XML para os membros e o bloco de extensão. O nó no extension bloco descreve o tipo estendido e o parâmetro receptor. O compilador C# copia esse nó para o membro gerado para todos os membros no bloco de extensão. Esses exemplos demonstram o estilo preferencial para gerar documentação XML para membros da extensão.
/// <summary>
/// Contains extension members for numeric sequences.
/// </summary>
public static class NumericSequences
{
/// <summary>
/// Defines extensions for integer sequences.
/// </summary>
/// <param name="sequence">The sequence used as a receiver.</param>
extension(IEnumerable<int> sequence)
{
/// <summary>
/// Adds a scalar value to each element in the sequence.
/// </summary>
/// <param name="operand">The amount to add.</param>
/// <returns>
/// A new sequence where each value contains the updated value.
/// </returns>
public IEnumerable<int> AddValue(int operand)
{
foreach (var item in sequence)
{
yield return item + operand;
}
}
/// <summary>
/// Gets the median value of the sequence.
/// </summary>
/// <remarks>
/// This value is calculated when requested.
/// </remarks>
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];
}
}
}
/// <summary>
/// Concatenates two sequences of integers into a single sequence.
/// </summary>
/// <remarks>The resulting sequence enumerates all elements from <paramref name="left"/> in order,
/// followed by all elements from <paramref name="right"/>. Enumeration is deferred and performed lazily as the
/// returned sequence is iterated.</remarks>
/// <param name="left">The first sequence of integers to concatenate. Cannot be null.</param>
/// <param name="right">The second sequence of integers to concatenate. Cannot be null.</param>
/// <returns>A sequence that contains the elements of the first sequence followed by the
/// elements of the second sequence.</returns>
public static IEnumerable<int> operator +(IEnumerable<int> left, IEnumerable<int> right)
=> left.Concat(right);
}
}
Define extension o receptor: sequence, que é um IEnumerable<int>. O tipo de receptor 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 sequenceambos.
Você acessa qualquer um dos membros da extensão como se fossem membros do tipo receptor:
IEnumerable<int> numbers = Enumerable.Range(1, 10);
numbers = numbers.AddValue(10);
var median = numbers.Median;
var combined = numbers + Enumerable.Range(100, 10);
Você pode declarar qualquer número de membros em um único bloco, desde que compartilhem o mesmo receptor. 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 do receptor. O parâmetro de extensão não precisará incluir o nome do parâmetro se os únicos membros forem estáticos:
/// <summary>
/// Provides static extensions for the <see cref="IEnumerable{Int32}"/> type.
/// </summary>
extension(IEnumerable<int>)
{
// Method:
/// <summary>
/// Generates a sequence of integers, starting from a specified value and incrementing by a given amount.
/// </summary>
/// <param name="low">The starting value of the sequence.</param>
/// <param name="count">The number of integers to generate. Must be non-negative.</param>
/// <param name="increment">The value by which to increment each subsequent integer in the sequence.</param>
/// <returns>
/// An enumerable collection of integers, beginning with the specified starting value and containing the
/// specified number of elements, each incremented by the given amount.
/// </returns>
public static IEnumerable<int> Generate(int low, int count, int increment)
{
for (int i = 0; i < count; i++)
yield return low + (i * increment);
}
// Property:
/// <summary>
/// Gets an empty sequence of integers representing the identity element for sequence operations.
/// </summary>
/// <remarks>
/// This property can be used as a neutral starting point when aggregating or composing
/// sequences of integers. The returned sequence is always empty and does not allocate any storage.
/// </remarks>
public static IEnumerable<int> Identity => Enumerable.Empty<int>();
}
Você chama extensões estáticas como se fossem membros estáticos do tipo receptor:
var newSequence = IEnumerable<int>.Generate(5, 10, 2);
var identity = IEnumerable<int>.Identity;
Você chama operadores como se fossem operadores definidos pelo usuário no tipo.
Importante
Uma extensão não introduz um escopo para declarações de membro. 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 receptor em seu nome para membros estáticos e o parâmetro receptor para membros da 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;
}
}
Você pode chamar o Add método de qualquer outro método como se ele 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 il (linguagem intermediária). Os chamadores não podem fazer uma distinção entre eles. Na verdade, você pode converter métodos de extensão existentes na sintaxe do novo membro sem uma alteração significativa. Os formatos são binários e compatíveis com a origem.
Blocos de extensão genéricos
Quando você especifica os parâmetros de tipo para um membro de extensão declarado em um bloco de extensão, depende de onde você precisa do parâmetro de tipo:
- Adicione o parâmetro de tipo à
extensiondeclaração quando o parâmetro de tipo for usado no receptor. - Adicione o parâmetro de tipo à declaração de membro quando o tipo for distinto de qualquer parâmetro de tipo especificado no receptor.
- Não é possível especificar o mesmo parâmetro de tipo em ambos os locais.
O exemplo a seguir mostra um bloco de extensão para IEnumerable<T> o qual dois dos membros da extensão exigem um segundo parâmetro de tipo:
/// <summary>
/// Contains generic extension members for sequences.
/// </summary>
public static class GenericExtensions
{
/// <summary>
/// Defines extensions for generic sequences.
/// </summary>
/// <typeparam name="TReceiver">The type of elements in the receiver sequence.</typeparam>
/// <param name="source">The sequence used as a receiver.</param>
extension<TReceiver>(IEnumerable<TReceiver> source)
{
/// <summary>
/// Returns a sequence containing a specified number of elements from the source, starting at a given index.
/// </summary>
/// <param name="start">The zero-based index at which to begin retrieving elements. Must be greater than or equal to 0.</param>
/// <param name="count">The number of elements to return. Must be greater than or equal to 0.</param>
/// <returns>
/// An <see cref="IEnumerable{TReceiver}"/> that contains up to <paramref name="count"/> elements from the
/// source sequence, starting at the element at position <paramref name="start"/>. If <paramref name="start"/>
/// is greater than the number of elements in the source, an empty sequence is returned.
/// </returns>
public IEnumerable<TReceiver> Spread(int start, int count)
=> source.Skip(start).Take(count);
/// <summary>
/// Returns a sequence that contains the elements of the original sequence followed by the elements of a
/// specified sequence, each transformed by a converter function.
/// </summary>
/// <remarks>
/// Enumeration of the returned sequence will not start until the sequence is iterated.
/// The converter function is applied to each element of the appended sequence as it is enumerated.
/// </remarks>
/// <typeparam name="TArg">The type of the elements in the sequence to append.</typeparam>
/// <param name="second">The sequence whose elements are to be appended after being converted. Cannot be null.</param>
/// <param name="Converter">A function to convert each element of the appended sequence to the result type. Cannot be null.</param>
/// <returns>
/// An IEnumerable<TReceiver> that contains the elements of the original sequence followed by the converted
/// elements of the specified sequence.
/// </returns>
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);
}
}
/// <summary>
/// Returns a sequence that consists of the elements of the specified collection, transformed by the provided
/// converter, followed by the elements of the current sequence.
/// </summary>
/// <remarks>
/// Enumeration of the returned sequence will not start until the sequence is iterated.
/// Both the input collection and the converter function must not be null; otherwise, an exception will be
/// thrown at enumeration time.
/// </remarks>
/// <typeparam name="TArg">The type of the elements in the collection to prepend.</typeparam>
/// <param name="second">The collection whose elements are to be transformed and prepended to the current sequence. Cannot be null.</param>
/// <param name="converter">A function to convert each element of the prepended collection to the target type. Cannot be null.</param>
/// <returns>
/// An IEnumerable<TReceiver> that contains the converted elements of the specified collection followed by the
/// elements of the current sequence.
/// </returns>
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 especificar Prepend o parâmetro de tipo extra para a conversão. Nenhum dos membros repete o parâmetro de tipo para o receptor.
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;
}
}
}
Consulte também
Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da Linguagem C# . A especificação de idioma é a fonte definitiva para a sintaxe e o uso de C#.