Menu

Métodos de extensão em C#: sintaxe, IEnumerable, LINQ e regras

Como os métodos de extensão funcionam em C#: um método static em uma classe static com this no primeiro parâmetro, chamado como se fosse um método de instância, estendendo string, enums e IEnumerable<T>, como o LINQ é construído com eles, por que métodos de instância vencem, receptores null e a diretiva using de que eles precisam.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Um método de extensão adiciona um método a um tipo existente sem modificá-lo, herdar dele ou envolvê-lo. Você escreve um método static comum, marca o primeiro parâmetro com this, e quem chama o usa com a sintaxe de ponto, como se o tipo sempre o tivesse tido:

Saída:

Extension methods let yo...
12
short
Extension...

title.Truncate(24) é compilado exatamente como StringExtensions.Truncate(title, 24). A sintaxe de ponto é só uma conveniência: o método continua static, continua em StringExtensions e só pode usar os membros públicos de string.

As regras para declarar um

  • O método precisa ser static e ficar em uma classe static, não genérica e não aninhada em outra classe. Uma classe não static ou genérica dá CS1106, Extension method must be defined in a non-generic static class; uma aninhada dá CS1109, Extension methods must be defined in a top level static class.
  • this vai só no primeiro parâmetro, e o tipo desse parâmetro é o tipo que está sendo estendido. Os demais parâmetros são normais.
  • O próprio método pode ser genérico (this IEnumerable<T> source), mesmo que a classe não possa.
  • Ele só enxerga o que qualquer código de fora enxerga: membros públicos (e, dentro do mesmo assembly, internos). Campos privados continuam privados.

A convenção é dar à classe o nome do que ela estende (StringExtensions, EnumerableExtensions) e agrupá-las em um namespace como MyApp.Extensions.

Estendendo interfaces e IEnumerable<T>

Estender uma interface adiciona o método a todos os tipos que a implementam. Estender IEnumerable<T>, portanto, dá o método de uma vez a arrays, listas, conjuntos, coleções de chaves de dicionários e resultados de consultas LINQ:

Saída:

9.5
50
65
f0 f3 f6

EveryNth é genérico: T é inferido a partir do receptor, então funciona para strings aqui e para qualquer outro tipo de elemento. Como usa yield return, ele é preguiçoso como os operadores LINQ embutidos e pode ser encadeado com eles.

O LINQ é construído com métodos de extensão

Where, Select, OrderBy, Sum, First e o resto do LINQ são métodos de extensão sobre IEnumerable<T>, definidos na classe static System.Linq.Enumerable. É por isso que using System.Linq; precisa estar no topo do arquivo antes de list.Where(...) compilar, e é por isso que o LINQ funciona em qualquer tipo de coleção sem que nenhuma delas implemente um método Where:

Saída:

72, 88, 95
72, 88, 95

As duas linhas são o mesmo código. A sintaxe de extensão transforma chamadas aninhadas, que se leem de dentro para fora, em uma cadeia que se lê na ordem em que os passos acontecem, e esse é o principal motivo de os métodos de extensão existirem: eles foram adicionados no C# 3 junto com o LINQ. A página de LINQ cobre os operadores.

O namespace precisa ser importado

Um método de extensão só está em escopo quando o namespace da sua classe static é importado com using. Sem isso, a chamada falha como se o método não existisse:

error CS1061: 'string' does not contain a definition for 'Truncate' and no accessible extension method 'Truncate' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)

Adicione o using do namespace das extensões (ou, a partir do C# 10, um global using uma vez para o projeto):

Saída:

EUR 49.90

O outro lado disso é útil: um método de extensão que você não quer em todo lugar pode ficar em um namespace que só alguns arquivos importam.

Métodos de instância vencem

Quando um tipo já tem um método de instância aplicável, o compilador o usa e nunca considera a extensão. Uma extensão com a mesma assinatura é código morto em toda chamada que usa a sintaxe de ponto:

Saída:

Invoice (instance method): 120
Invoice (extension): 120 USD
extension: only reachable as a static call

A extensão de um argumento perdeu para o método de instância; a de dois argumentos foi usada porque nenhum método de instância recebe uma string. Isso também significa que uma atualização de biblioteca que adicione um método de instância com o nome da sua extensão passa suas chamadas para a versão da biblioteca sem aviso. Escolha nomes de extensão com pouca chance de colidir.

Chamando em null

Como a chamada é, na verdade, uma chamada static, um receptor null não lança exceção no ponto da chamada. O método recebe null como primeiro argumento e decide o que isso significa:

Saída:

True
AL
ArgumentNullException: fullName

missing.IsBlank() retornar true é conveniente, mas pode surpreender quem espera que uma chamada de membro em null lance exceção. Aceite null só em métodos cujo nome deixa isso óbvio (IsBlank, OrEmpty); nos outros, lance ArgumentNullException como o LINQ faz.

Estendendo enums

Enums não podem ter métodos próprios, o que os torna um alvo natural:

enum OrderStatus { Pending, Paid, Shipped, Delivered, Cancelled }

static class OrderStatusExtensions
{
    public static bool IsFinal(this OrderStatus s) =>
        s == OrderStatus.Delivered || s == OrderStatus.Cancelled;
}

// usage: if (order.Status.IsFinal()) { ... }

Membros de extensão no C# 14

Até o C# 14, só métodos podiam ser extensões. O C# 14 adicionou os blocos extension, que agrupam membros para um receptor e permitem também propriedades de extensão e membros static:

// C# 14 and later
public static class StringExtensions
{
    extension(string s)
    {
        public bool IsBlank => string.IsNullOrWhiteSpace(s);   // extension property
        public string Truncate(int max) => s.Length <= max ? s : s[..max] + "...";
    }
}

Os métodos de extensão clássicos com parâmetro this continuam válidos e são o que quase todo o código e as bibliotecas existentes usam.

Quando escrever um

Métodos de extensão servem quando você não é dono do tipo (string, DateTime, interfaces do framework), quando você quer o mesmo auxiliar em todas as implementações de uma interface, ou quando uma cadeia se lê melhor que chamadas aninhadas. Quando a classe é sua, adicione um método de instância de verdade: ele pode usar estado privado e aparece onde os leitores o procuram. E evite estender object, o que coloca seu método na lista de autocompletar de todo valor do programa.

Perguntas frequentes

O que é um método de extensão em C#?

Um método static que pode ser chamado como se fosse um método de instância de outro tipo. Você o escreve em uma classe static e coloca this antes do primeiro parâmetro: public static bool IsBlank(this string s) => string.IsNullOrWhiteSpace(s);. Aí name.IsBlank() funciona em qualquer string, sem mudar nem herdar de string.

Por que meu método de extensão não é encontrado?

Métodos de extensão só ficam visíveis quando o namespace deles é importado. Se a classe static está em MyApp.Extensions, adicione using MyApp.Extensions; ao arquivo que faz a chamada; senão o compilador reporta CS1061, dizendo que o tipo não contém nenhuma definição nem método de extensão acessível com esse nome. Verifique também se a classe é static, não genérica e não aninhada.

Um método de extensão pode substituir um método de instância?

Não. O compilador procura primeiro um método de instância aplicável e só considera métodos de extensão quando não existe nenhum. Uma extensão com o mesmo nome e os mesmos parâmetros de um método de instância é ignorada sem aviso em toda chamada que usa a sintaxe de instância.

Dá para chamar um método de extensão em null em C#?

Sim. A chamada é compilada como uma chamada de método static com null como primeiro argumento, então nenhuma NullReferenceException é lançada na chamada em si. O método decide o que fazer: tratar null como entrada válida (como um auxiliar no estilo IsNullOrEmpty) ou lançar ArgumentNullException, como fazem os métodos do LINQ.

Existem propriedades de extensão em C#?

Não antes do C# 14. Até lá, só métodos podem ser extensões, então o que seria uma propriedade é escrito como método (GetFullName()). O C# 14 adicionou os blocos de extensão, que podem declarar propriedades de extensão e membros static, além de métodos.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR