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.
Os membros da extensão permitem que você "adicione" métodos aos tipos existentes sem criar um novo tipo derivado, recompilar ou modificar o tipo original.
A partir do C# 14, há duas sintaxes que você usa para definir métodos de extensão. O C# 14 adiciona extension
contêineres, em que você define vários membros de extensão para um tipo ou uma instância de um tipo. Antes do C# 14, você adiciona o this
modificador ao primeiro parâmetro de um método estático para indicar que o método aparece como um membro de uma instância do tipo de parâmetro.
Métodos de extensão são métodos estáticos, mas são chamados como se fossem métodos de instância no tipo estendido. Para o código do cliente escrito em C#, F# e Visual Basic, não há nenhuma diferença aparente entre chamar um método de extensão e os métodos definidos em um tipo. Ambas as formas de métodos de extensão são compiladas para o mesmo IL (Linguagem Intermediária). Os consumidores de membros da extensão não precisam saber qual sintaxe foi usada para definir métodos de extensão.
Os membros de extensão mais comuns são os operadores de consulta padrão LINQ que adicionam funcionalidade de consulta aos tipos System.Collections.IEnumerable e System.Collections.Generic.IEnumerable<T> existentes. Para usar os operadores de consulta padrão, primeiro coloque-os no escopo com uma diretiva using System.Linq
. Em seguida, qualquer tipo que implementar IEnumerable<T> parece ter métodos de instância, como GroupBy, OrderBy, Average, e assim por diante. Você pode ver esses métodos extras no complemento de instrução do IntelliSense ao digitar "ponto" após uma instância de um tipo IEnumerable<T>, como List<T> ou Array.
Exemplo de OrderBy
O exemplo a seguir mostra como chamar o método de operador OrderBy
de consulta padrão em uma matriz de inteiros. A expressão entre parênteses é uma expressão lambda. Muitos operadores de consulta padrão tomam expressões lambda como parâmetros. Para obter mais informações, consulte Expressões Lambda.
int[] numbers = [10, 45, 15, 39, 21, 26];
IOrderedEnumerable<int> result = numbers.OrderBy(g => g);
foreach (int i in result)
{
Console.Write(i + " ");
}
//Output: 10 15 21 26 39 45
Os métodos de extensão são definidos como métodos estáticos, mas são chamados usando a sintaxe do método de instância. Seu primeiro parâmetro especifica em qual tipo o método opera. O parâmetro segue este modificador. Os métodos de extensão só estão no escopo quando você importa explicitamente o namespace para o código-fonte com a diretiva using
.
Declarar membros da extensão
A partir do C# 14, você pode declarar blocos de extensão. Um bloco de extensão é um bloco em uma classe estática não aninhada, não genérica que contém membros de extensão para um tipo ou uma instância desse tipo. O exemplo de código a seguir define um bloco de extensão para o string
tipo. O bloco de extensão contém um membro: um método que conta as palavras na cadeia de caracteres:
namespace CustomExtensionMembers;
public static class MyExtensions
{
extension(string str)
{
public int WordCount() =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
}
Antes do C# 14, você declara um método de extensão adicionando o this
modificador ao primeiro parâmetro:
namespace CustomExtensionMethods;
public static class MyExtensions
{
public static int WordCount(this string str) =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
Ambas as formas de extensões devem ser definidas dentro de uma classe estática não aninhada e não genérica.
E ele pode ser chamado de um aplicativo usando a sintaxe para acessar membros da instância:
string s = "Hello Extension Methods";
int i = s.WordCount();
Embora os membros da extensão adicionem novos recursos a um tipo existente, os membros da extensão não violam o princípio do encapsulamento. As declarações de acesso para todos os membros do tipo estendido se aplicam aos membros da extensão.
A classe MyExtensions
e o método WordCount
são static
, e podem ser acessados como todos os outros membros static
. O WordCount
método pode ser invocado como outros static
métodos da seguinte maneira:
string s = "Hello Extension Methods";
int i = MyExtensions.WordCount(s);
O código C# anterior se aplica tanto ao bloco de extensão quanto à sintaxe this
para membros de extensão. O código anterior:
- Declara e atribui um novo
string
nomes
com um valor de"Hello Extension Methods"
. - Chama
MyExtensions.WordCount
com o argumento dados
.
Para obter mais informações, consulte Como implementar e chamar um método de extensão personalizado.
Em geral, você provavelmente chama os membros da extensão com muito mais frequência do que os implementa. Como os membros da extensão são invocados como se fossem declarados como membros da classe estendida, nenhum conhecimento especial é necessário para usá-los pelo código cliente. Para habilitar membros de extensão para um tipo específico, basta adicionar uma using
diretiva para o namespace no qual os métodos são definidos. Por exemplo, para usar os operadores de consulta padrão, adicione esta using
diretiva ao seu código:
using System.Linq;
Vincular membros de extensão durante a compilação
Você pode usar membros de extensão para estender uma classe ou interface, mas não para substituir o comportamento definido em uma classe. Um membro de extensão com o mesmo nome e assinatura que os de uma interface ou de uma classe nunca é chamado. No tempo de compilação, os membros de extensão sempre têm menor prioridade do que os membros de instância (ou estáticos) definidos no próprio tipo. Em outras palavras, se um tipo tiver um método chamado Process(int i)
e você tiver um método de extensão com a mesma assinatura, o compilador sempre se associará ao método de membro. Quando o compilador encontra uma invocação de membro, ele primeiro procura um correspondente nos membros do tipo. Se nenhuma correspondência for encontrada, ela procurará todos os membros da extensão definidos para o tipo. Ele se associa ao primeiro membro de extensão encontrado. O exemplo a seguir demonstra as regras que o compilador C# segue para determinar se deve ser associado a um membro de instância no tipo ou a um membro de extensão. A classe Extensions
estática contém membros de extensão definidos para qualquer tipo que implemente IMyInterface
:
public interface IMyInterface
{
void MethodB();
}
// Define extension methods for IMyInterface.
// The following extension methods can be accessed by instances of any
// class that implements IMyInterface.
public static class Extension
{
public static void MethodA(this IMyInterface myInterface, int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public static void MethodA(this IMyInterface myInterface, string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public static void MethodB(this IMyInterface myInterface) =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
As extensões equivalentes podem ser declaradas usando a sintaxe do membro da extensão C# 14:
public static class Extension
{
extension(IMyInterface myInterface)
{
public void MethodA(int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public void MethodA(string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public void MethodB() =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
}
Classes A
e B
C
todos implementam a interface:
// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
class A : IMyInterface
{
public void MethodB() { Console.WriteLine("A.MethodB()"); }
}
class B : IMyInterface
{
public void MethodB() { Console.WriteLine("B.MethodB()"); }
public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
}
class C : IMyInterface
{
public void MethodB() { Console.WriteLine("C.MethodB()"); }
public void MethodA(object obj)
{
Console.WriteLine("C.MethodA(object obj)");
}
}
O MethodB
método de extensão nunca é chamado porque seu nome e assinatura correspondem exatamente aos métodos já implementados pelas classes. Quando o compilador não consegue encontrar um método de instância com uma assinatura correspondente, ele se associa a um método de extensão correspondente se houver.
// Declare an instance of class A, class B, and class C.
A a = new A();
B b = new B();
C c = new C();
// For a, b, and c, call the following methods:
// -- MethodA with an int argument
// -- MethodA with a string argument
// -- MethodB with no argument.
// A contains no MethodA, so each call to MethodA resolves to
// the extension method that has a matching signature.
a.MethodA(1); // Extension.MethodA(IMyInterface, int)
a.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// A has a method that matches the signature of the following call
// to MethodB.
a.MethodB(); // A.MethodB()
// B has methods that match the signatures of the following
// method calls.
b.MethodA(1); // B.MethodA(int)
b.MethodB(); // B.MethodB()
// B has no matching method for the following call, but
// class Extension does.
b.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// C contains an instance method that matches each of the following
// method calls.
c.MethodA(1); // C.MethodA(object)
c.MethodA("hello"); // C.MethodA(object)
c.MethodB(); // C.MethodB()
/* Output:
Extension.MethodA(this IMyInterface myInterface, int i)
Extension.MethodA(this IMyInterface myInterface, string s)
A.MethodB()
B.MethodA(int i)
B.MethodB()
Extension.MethodA(this IMyInterface myInterface, string s)
C.MethodA(object obj)
C.MethodA(object obj)
C.MethodB()
*/
Padrões de uso comuns
Funcionalidade de Coleção
No passado, era comum criar "Classes de Coleção" que implementavam a System.Collections.Generic.IEnumerable<T> interface para um determinado tipo e continham funcionalidades que atuavam em coleções desse tipo. Embora não haja nada de errado com a criação desse tipo de objeto de coleção, a mesma funcionalidade pode ser obtida usando uma extensão no System.Collections.Generic.IEnumerable<T>. As extensões têm a vantagem de permitir que a funcionalidade seja chamada de qualquer coleção, como uma System.Array ou System.Collections.Generic.List<T> que implementa System.Collections.Generic.IEnumerable<T> nesse tipo. Um exemplo disso usando uma Matriz de Int32 pode ser encontrado anteriormente neste artigo.
Funcionalidade do Layer-Specific
Ao usar uma Arquitetura onion ou outro design de aplicativo em camadas, é comum ter um conjunto de entidades de domínio ou objetos de transferência de dados que podem ser usados para se comunicar entre os limites do aplicativo. Esses objetos geralmente não contêm nenhuma funcionalidade ou apenas funcionalidade mínima que se aplica a todas as camadas do aplicativo. Os métodos de extensão podem ser usados para adicionar funcionalidades específicas a cada camada de aplicativo.
public class DomainEntity
{
public int Id { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
}
static class DomainEntityExtensions
{
static string FullName(this DomainEntity value)
=> $"{value.FirstName} {value.LastName}";
}
Você pode declarar uma propriedade equivalente FullName
em C# 14 e posterior usando a nova sintaxe de bloco de extensão:
static class DomainEntityExtensions
{
extension(DomainEntity value)
{
string FullName => $"{value.FirstName} {value.LastName}";
}
}
Estendendo tipos predefinidos
Em vez de criar novos objetos quando a funcionalidade reutilizável precisa ser criada, muitas vezes você pode estender um tipo existente, como um tipo .NET ou CLR. Por exemplo, se você não usar métodos de extensão, poderá criar uma Engine
ou Query
classe para executar uma consulta em um SQL Server que pode ser chamada de vários locais em nosso código. No entanto, você pode estender a System.Data.SqlClient.SqlConnection classe usando métodos de extensão para executar essa consulta de qualquer lugar que você tenha uma conexão com um SQL Server. Outros exemplos podem ser adicionar funcionalidades comuns à System.String classe, estender os recursos de processamento de dados do System.IO.Stream objeto e System.Exception objetos para funcionalidades específicas de tratamento de erros. Esses tipos de casos de uso são limitados apenas por sua imaginação e bom senso.
Estender tipos predefinidos pode ser difícil com struct
tipos porque eles são passados por valor aos métodos. Isso significa que todas as alterações no struct são feitas em uma cópia do struct. Essas alterações não serão visíveis quando o método de extensão for encerrado. Você pode adicionar o ref
modificador ao primeiro argumento tornando-o um ref
método de extensão. A ref
palavra-chave pode aparecer antes ou depois da this
palavra-chave sem diferenças semânticas. Adicionar o ref
modificador indica que o primeiro argumento é passado por referência. Essa técnica permite que você escreva métodos de extensão que alterem o estado do struct que está sendo estendido (observe que os membros privados não estão acessíveis). Somente tipos de valor ou tipos genéricos restritos ao struct (para obter mais informações sobre essas regras, consulte struct
a restrição para obter mais informações) são permitidos como o primeiro parâmetro de um ref
método de extensão ou como o receptor de um bloco de extensão. O exemplo a seguir mostra como usar um ref
método de extensão para modificar diretamente um tipo interno sem a necessidade de reatribuir o resultado ou passá-lo por uma função com a ref
palavra-chave:
public static class IntExtensions
{
public static void Increment(this int number)
=> number++;
// Take note of the extra ref keyword here
public static void RefIncrement(this ref int number)
=> number++;
}
Os blocos de extensão equivalentes são mostrados no seguinte código:
public static class IntExtensions
{
extension(int number)
{
public void Increment()
=> number++;
}
// Take note of the extra ref keyword here
extension(ref int number)
{
public void RefIncrement()
=> number++;
}
}
Diferentes blocos de extensão são necessários para distinguir os modos de parâmetro "by-value" e "by-ref" para o receptor.
Você pode ver a diferença que a aplicação ref
ao receptor tem no exemplo a seguir:
int x = 1;
// Takes x by value leading to the extension method
// Increment modifying its own copy, leaving x unchanged
x.Increment();
Console.WriteLine($"x is now {x}"); // x is now 1
// Takes x by reference leading to the extension method
// RefIncrement changing the value of x directly
x.RefIncrement();
Console.WriteLine($"x is now {x}"); // x is now 2
Você pode aplicar a mesma técnica adicionando ref
membros de extensão aos tipos de estrutura definidos por usuários.
public struct Account
{
public uint id;
public float balance;
private int secret;
}
public static class AccountExtensions
{
// ref keyword can also appear before the this keyword
public static void Deposit(ref this Account account, float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
O exemplo anterior também pode ser criado usando blocos de extensão no C# 14:
public static class AccountExtensions
{
extension(ref Account account)
{
// ref keyword can also appear before the this keyword
public void Deposit(float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
}
Você pode acessar esses métodos de extensão da seguinte maneira:
Account account = new()
{
id = 1,
balance = 100f
};
Console.WriteLine($"I have ${account.balance}"); // I have $100
account.Deposit(50f);
Console.WriteLine($"I have ${account.balance}"); // I have $150
Diretrizes gerais
É preferível adicionar funcionalidade modificando o código de um objeto ou derivando um novo tipo sempre que for razoável e possível fazer isso. Os métodos de extensão são uma opção crucial para criar funcionalidade reutilizável em todo o ecossistema do .NET. Os membros da extensão são preferíveis quando a origem original não está sob seu controle, quando um objeto derivado é inadequado ou impossível, ou quando a funcionalidade tem escopo limitado.
Para obter mais informações sobre tipos derivados, consulte Herança.
Se você implementar métodos de extensão para um determinado tipo, lembre-se dos seguintes pontos:
- Um método de extensão não será chamado se ele tiver a mesma assinatura que um método definido no tipo.
- Os métodos de extensão são colocados no escopo no nível do namespace. Por exemplo, se você tiver várias classes estáticas que contêm métodos de extensão em um único namespace nomeado
Extensions
, todas elas serão colocadas no escopo pelausing Extensions;
diretiva.
Para uma biblioteca de classes que você implementou, você não deve usar métodos de extensão para evitar incrementar o número de versão de um assembly. Se você quiser adicionar uma funcionalidade significativa a uma biblioteca para a qual possui o código-fonte, siga as diretrizes do .NET para controle de versão do assembly. Para obter mais informações, consulte o Controle de Versão do Assembly.
Consulte também
- Exemplos de programação paralela (muitos exemplos demonstram métodos de extensão)
- Expressões Lambda
- Visão geral dos operadores de consulta padrão
- Regras de conversão para parâmetros de instância e seu impacto
- Interoperabilidade de métodos de extensão entre idiomas
- Métodos de extensão e delegados curried
- Método de extensão: associação e relatório de erros