C# 14 - Apresentando Extension Members
![]() |
Hoje vou apresentar o novo recurso do C# 14 : Extension Members |
O novo recurso Extension Members do C# 14 expande o conceito dos métodos de extensão, permitindo adicionar propriedades, índices, métodos de instância e até membros estáticos associados ao tipo estendido
Vamos iniciar recordando um pouco sobre os métodos de extensão (que foram incluídos no C# 3.0)
Temos a seguir o código que representa a sintaxe usada para criar um método de extensão:
|
public static class NomeDaClasseDeExtensao { public static TipoRetorno NomeDoMetodo(this TipoAlvo instancia, ParametrosExtras...) { // corpo do método } } |
Nesta sintaxe :
- Temos que criar uma classe estática onde vamos criar os
métodos de extensão,
- Os métodos de extensão tem que ser estáticos.
-
Sendo que o primeiro parâmetro do método deve especificar o tipo no qual o
método opera e ele deve ser precedido pelo modificador this.
O this serve para informar ao compilador que este primeiro parâmetro representa a instância que está sendo estendida e que o método deve ser tratado como se fosse um método de instância, mesmo sendo estático.
Com exemplo temos um método de extensão ContaPalavras que opera sobre o tipo string
|
public static class StringExtension { public static int ContaPalavras(this String texto) { return texto.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length; } } |
oNeste código criamos a classe estática StringExtensions, e, a seguir o método
estático ContaPalavras, definindo o tipo alvo como string precedido do
modificador this.
Para usar esse método como se fosse um método de
instância do tipo string podemos fazer assim:
string texto =
“Membros de extensão no C# 14”;
int numeroPalavras = texto.ContaPalavras();
Embora seja um recurso poderoso os métodos de extensão possuem
limitações como
- Funcionarem somente para métodos, e apenas como açúcar
sintático para uma chamada estática
- E além disso temos que usar o namespace
da classe de extensão importado via using
Assim os métodos de extensão
agem sobe a instância do tipo e ficam em classes externas e não modificam
realmente o tipo — são apenas resolvidos pelo compilador. O método é estático,
mas se comporta como se fosse de instância, ou seja, os métodos de extensão não
ficam dentro do tipo.
Eles sempre ficam em uma classe externa que
precisa ser estática e estar dentro de algum namespace.
Extension Members
Os Membros de extensão - do C# 14 expande o antigo modelo de métodos de extensão
e permite adicionar propriedades, indexadores, eventos, métodos de instância e
até membros estáticos associados diretamente ao tipo estendido — tudo isso sem
modificar a classe original
Agora com os extension members podemos
adicionar propriedades, métodos e membros estáticos
diretamente associados ao
tipo, e não apenas ao objeto — e sem precisar de classes externas.
Nota: Embora tenham sidos previstos, os
Eventos, indexadores e outros membros, ainda não fazem parte
da implementação atual disponível a partir de 15 de novembro
Vejamos
a sintaxe dos Membros de extensão expressa neste código:
public static class <ClasseDeExtensao> { // ---- Membros de instância ---- extension(<TipoAlvo> instancia) { public <Tipo> Propriedade => ...; public <Retorno> Metodo(<params>) { ... } public <Tipo> this[int index] => ...; public event EventHandler Evento; } // ---- Membros estáticos ---- extension(<TipoAlvo>) { public static <Tipo> PropriedadeEstatica => ...; public static <Retorno> MetodoEstatico(<params>) { ... } } } |
Observe que eles são organizados em blocos extension(...)
dentro de uma classe estática onde podem existir dois tipos de blocos:
1) Bloco de instância
Usado para adicionar propriedades,
métodos, indexadores e eventos que se comportam como se fossem membros de
instância do tipo alvo.
Aqui o parâmetro (ex.: instancia)
representa a instância real do tipo (e por isso não precisamos mais usar o
this)
2) Bloco estático associado ao tipo usado
para adicionar membros estáticos, como:
- propriedades estáticas
-
métodos de fábrica
- constantes
- objetos padrão
- validadores globais
A ausência do parâmetro indica que estamos anexando membros ao TIPO, não ao
objeto.
Note que não é mais preciso usar o modificador this
nos extension members porque agora não estamos estendendo métodos, e sim
declarando blocos de extensão que já definem claramente se os membros são de
instância ou estáticos.
Exemplo prático
Vejamos agora um exemplo prático onde dada uma classe Pessoa com Nome do tipo string e Idade do tipo int:
| public class Pessoa { public string Nome { get; set; } = ""; public int Idade { get; set; } } |
Vamos mostrar a implementação dos extensions members criando um método e uma propriedade de extensão e também definindo um membro estático vinculado ao tipo Pessoa. Todos eles definidos fora da classe original, usando blocos de extensão.
Vamos criar a classe PessoaExtensions definindo o seguinte código:
public static class PessoaExtensions { // ----------------------------- // 1) Membros de instância // ----------------------------- extension(Pessoa p) { // Método de instância (extension member) public string Describe() => $"{p.Nome}, {p.Idade} anos"; // Propriedade de instância (extension member) public bool IsAdult => p.Idade >= 18; } // ----------------------------- // 2) Membros estáticos associados ao tipo // ----------------------------- extension(Pessoa) { public static Pessoa Default => new Pessoa { Nome = "Desconhecido", Idade = 0 }; } } |
Criamos dois blocos extension onde no primeiro bloco declaramos que estamos
criando **membros de instância** associados ao tipo Pessoa.
A variável p
representa a instância real de Pessoa sobre a qual esses membros irão atuar.
O primeiro membro que implementamos foi a propriedade de instância IsAdult
que retorna true se a idade da Pessoa for maior ou igual a 18:
public bool IsAdult => p.Idade >= 18;
A seguir no mesmo bloco, criamo o método de instância Describe que retorna uma string descrevendo a instancia da Pessoa com nome e idade:
public string Describe() => $"{p.Nome}, {p.Idade} anos";
No segundo bloco extension não definimos uma variável de instância. Isso indica ao compilador que este bloco define **membros estáticos associados ao tipo** Pessoa, e não a uma instância específica.
Dentro deste bloco vamos criamos o membro estático Default que retorna uma
instância padrão de Pessoa com Nome 'Desconhecido' e idade zero para um tipo
Pessoa:
public static Pessoa Default => new Pessoa { Nome =
"Desconhecido", Idade = 0 };
Com isso, concluímos a
implementação dos extension members:
• IsAdult → propriedade de
instância
• Describe → método de instância
• Default → membro estático
associado ao tipo Pessoa
E tudo isso sem alterar a classe
original Pessoa, ou seja, o membro estático não fica na classe de extensão; ele
aparece como se fizesse parte do tipo original.
É por isso que hoje
podemos escrever: var p = Pessoa.Default; mesmo que o
código de extensão esteja em outro arquivo.
Agora que já implementamos os extension members para a classe Pessoa, vamos ver na prática como utilizá-los dentro da classe Program:
using CSharp_ExtensionMembers;
Console.WriteLine("=== Demonstração Extension Members (C# 14 / .NET 10) ===");
Console.WriteLine("\nCriando objeto p : Maria com 20 anos");
var p = new Pessoa { Nome = "Maria", Idade = 20 };
// ----------------------
// MÉTODO DE EXTENSÃO
// ----------------------
Console.WriteLine("\nMétodo de Extensão: Describe()");
Console.WriteLine(p.Describe());
Console.WriteLine("\nPressione algo para continuar");
Console.ReadKey();
// ----------------------
// PROPRIEDADE DE EXTENSÃO
// ----------------------
Console.WriteLine("\nPropriedade de Extensão: IsAdult");
Console.WriteLine($"É adulta? {p.IsAdult}");
Console.WriteLine("\nPressione algo para continuar\n");
Console.ReadKey();
// ---------------------------------
// MEMBRO ESTÁTICO ASSOCIADO AO TIPO
// ----------------------------------
Console.WriteLine("Membro Estático Associado ao Tipo: Default");
var padrao = Pessoa.Default; // <-- Direto no tipo Pessoa!
Console.WriteLine($"Default: {padrao.Describe()}");
Console.WriteLine("\n--- Fim da demonstração ---");
Console.ReadKey();
Primeiro, usamos o método de instância que criamos, o Describe().
Mesmo ele estando fora da classe Pessoa, eu posso chamá-lo
diretamente da instância:
Console.WriteLine(p.Describe());
O compilador trata esse método como se ele fizesse parte da classe
original.
Em seguida, vamos usar a propriedade de extensão
IsAdult. Ela também aparece naturalmente no objeto:
Console.WriteLine(p.IsAdult);
Agora o membro estático associado
ao tipo, definido no bloco extension(Pessoa). Diferente dos
anteriores, esse membro pertence ao tipo, e não a uma instância. Por isso o
acesso é direto no nome da classe:
var padrao = Pessoa.Default;
Esse Default não existe dentro da classe original
Pessoa, mas graças aos extension members, ele aparece como se fizesse
parte do tipo.
Isso possibilita criar valores padrão, métodos fábrica e
utilidades associadas ao tipo de maneira organizada, sem alterar a classe
original.
Por fim, como Default retorna uma nova
Pessoa, eu posso usá-la normalmente:
Console.WriteLine(padrao.Describe());
Dessa forma, vimos o uso dos três tipos de extensão que criamos:
• método de instância
• propriedade de instância
• membro estático
associado ao tipo
Todos funcionando de forma natural, como se
fossem da própria classe Pessoa, embora tenham sido definidos externamente
através dos extension members.
Essa é exatamente a grande vantagem desse
recurso.
Pegue o projeto no github :
https://github.com/macoratti/CSharp_ExtensionMembers
E estamos conversados...
"Quem é fiel no
mínimo, também é fiel no muito; quem é injusto no mínimo, também é injusto no
muito."
Lucas 16:10
Referências:
NET - Unit of Work - Padrão Unidade de ...