Construtores e eventos parciais no C# 14
O C# 14 permite declarar construtores de instância e eventos como membros parciais, dividindo definições entre arquivos para uma geração de código mais limpa e melhor separação de responsabilidades.
O C# 14 introduz uma nova capacidade de declarar construtores de instância e eventos como membros partial. Isso significa que você pode dividir a definição de um construtor ou evento em duas partes de uma classe parcial, de forma similar ao que o C# já permite há muito tempo com métodos parciais e propriedades parciais. Uma parte de uma classe parcial pode declarar um construtor ou evento, e outra parte pode implementá-lo. Isso é especialmente útil em cenários como geração de código, onde um arquivo pode ser gerado automaticamente e outro mantido manualmente por um desenvolvedor.
Construtores parciais no C# 14
Os construtores parciais permitem dividir o construtor de uma classe em duas declarações dentro de uma classe partial. Uma declaração fornece apenas a assinatura (uma declaração definidora) e outra fornece o corpo de fato (uma declaração implementadora). O compilador as funde para que se comportem como um único construtor em tempo de execução.
Regras principais para construtores parciais:
- Exatamente uma declaração definidora e uma implementadora: Você precisa ter uma parte da classe parcial que declara o construtor (terminando em ponto e vírgula sem corpo) e outra parte que fornece a implementação (com o bloco de código do construtor). As assinaturas (tipos de parâmetros, nomes etc.) precisam coincidir exatamente entre as duas. Se faltar a parte definidora ou implementadora (ou se houver duplicatas), o código não compila.
- Apenas em classes parciais: Ambas as declarações precisam estar na mesma classe, que por sua vez precisa estar marcada como
partial. Você não pode ter um construtor parcial em uma classe que não seja parcial. - Inicializador de construtor apenas na implementação: Se o construtor precisar chamar outro construtor ou um construtor da classe base usando
: this(...)ou: base(...), esse inicializador só pode aparecer na declaração implementadora. A parte definidora (somente assinatura) não pode incluir nenhum inicializador de construtor. Na prática, qualquer chamadathis(...)oubase(...)é escrita no cabeçalho do construtor implementador. - Sintaxe de construtor primário em apenas um arquivo: O C# 14 permite usar construtores primários (parâmetros do construtor na declaração da classe) com classes parciais, mas apenas um arquivo parcial pode incluir a lista de parâmetros. Em outras palavras, se você decidir usar a forma concisa de construtor primário em uma classe parcial, precisa colocá-la em uma única declaração parcial da classe; outras declarações parciais da mesma classe não devem repetir nem declarar sua própria lista de parâmetros.
A seguir há um exemplo de um construtor parcial em ação. Imagine uma classe dividida em dois arquivos: uma parte autogerada declara um construtor e outra parte o implementa:
// File: Car.AutoGenerated.cs (auto-generated partial class)
public partial class Car
{
// Defining partial constructor (signature only, no body)
public partial Car(string model);
}
// File: Car.cs (developer-maintained partial class)
public partial class Car
{
// Implementing partial constructor (actual body)
public partial Car(string model)
{
// (Optional) Can call another constructor or base constructor here
// : base() or : this(...) would be placed after the parameter list if needed.
if (string.IsNullOrEmpty(model))
throw new ArgumentException("Model must be provided", nameof(model));
Console.WriteLine($"Car model set to {model}");
}
}
Neste exemplo, o construtor da classe Car está dividido: a primeira parte declara que Car(string model) existe, e a segunda parte fornece a implementação que valida o argumento e imprime uma mensagem. A declaração definidora não pode incluir código; é apenas a assinatura terminando em ;. A declaração implementadora tem o corpo e também poderia incluir uma chamada à classe base (se Car herdasse de outra classe) ou chamar outro construtor via this(). Em tempo de compilação, o compilador garante que haja exatamente uma de cada parte e então trata o construtor como um único construtor unificado.
Eventos parciais no C# 14
Os eventos parciais permitem uma divisão similar para eventos em uma classe parcial. Uma parte da classe define o evento (sem lógica de add/remove, igual a uma declaração de evento automática normal), e outra parte fornece os acessores add/remove (o código que executa quando assinantes anexam ou removem manipuladores de evento).
Regras principais para eventos parciais:
- Exatamente uma declaração definidora e uma implementadora: Assim como nos construtores, um evento marcado como
partialprecisa ser declarado exatamente duas vezes na classe parcial: uma definição sem corpo e uma implementação com os corpos dos acessores. Eles precisam ter o mesmo tipo e nome de evento e aparecer na mesma classe parcial. Você não pode ter peças adicionais de evento parcial além dessas duas. - A declaração definidora é semelhante a um campo: A parte definidora de um evento parcial é escrita como você normalmente declararia um evento respaldado por um campo, por exemplo:
public partial event EventHandler SomethingHappened;(com ponto e vírgula e sem bloco de acessores). Entretanto, por ser parcial, o compilador não autogera nenhum campo de respaldo nem lógica padrão de add/remove para esse evento. Trata-se essencialmente de um marcador indicando “haverá um evento com este nome e tipo”. - A declaração implementadora precisa ter acessores: A parte implementadora do evento parcial precisa fornecer tanto o acessor
addquanto oremoveem um bloco de evento, igual a um evento implementado manualmente. É aqui que você escreve o que acontece quando ouvintes assinam ou cancelam a assinatura. Como a parte definidora não tem campo de respaldo implícito, sua implementação normalmente precisa gerenciar o armazenamento de assinantes (por exemplo, via um campo privado, lista ou outro mecanismo) ou encaminhar para outra fonte de eventos. - Apenas em classes parciais (e não como abstratos ou implementações de interface): Assim como nos construtores parciais, eventos parciais só podem ser usados dentro de uma classe marcada como
partial. Você também não pode marcar um evento parcial comoabstract, e não pode usarpartialpara implementar o evento de uma interface; o recurso destina-se a divisão dentro de uma classe, não através de fronteiras de interface.
Veja um exemplo demonstrando um evento parcial. Um arquivo declara o evento e outro arquivo implementa a lógica personalizada de add/remove:
// File: Sensor.AutoGenerated.cs (auto-generated partial class)
public partial class Sensor
{
// Defining partial event (no body, just declaration)
public partial event EventHandler DataReceived;
}
// File: Sensor.cs (developer-maintained partial class)
public partial class Sensor
{
// Implementing partial event (with custom add/remove logic)
private EventHandler _dataReceivedHandlers; // backing storage for event handlers
public partial event EventHandler DataReceived
{
add
{
Console.WriteLine("Subscriber added to DataReceived");
_dataReceivedHandlers += value;
}
remove
{
Console.WriteLine("Subscriber removed from DataReceived");
_dataReceivedHandlers -= value;
}
}
// Optional: a method to raise the event safely from this class
protected void OnDataReceived(EventArgs e)
{
_dataReceivedHandlers?.Invoke(this, e);
}
}
Neste exemplo, a classe Sensor tem um evento DataReceived dividido entre dois arquivos. A parte autogerada da classe declara que existe um evento DataReceived do tipo EventHandler. A parte do desenvolvedor fornece a implementação real: define um campo privado para armazenar os assinantes e implementa os blocos add e remove para registrar uma mensagem e atualizar esse campo privado. Quando outro código faz sensor.DataReceived += Handler;, ele executa a lógica personalizada de add. Da mesma forma, cancelar a assinatura aciona a lógica personalizada de remove. A classe pode usar _dataReceivedHandlers para disparar o evento (por exemplo, no método OnDataReceived). Sem a parte implementadora, a declaração definidora sozinha não compilaria, já que o evento não tem respaldo por si só; o C# exige que a parte implementadora complete a funcionalidade do evento.
Comparação com versões anteriores do C#
Antes do C# 14, você não podia declarar construtores ou eventos como parciais. No C# 13 e anteriores, a palavra-chave partial só era válida em classes, métodos e (a partir do C# 13) propriedades/indexadores. Se você tentasse marcar um construtor ou um evento como parcial, o compilador produzia um erro. Essa limitação significava que não havia como dividir a implementação de um construtor ou evento entre arquivos diferentes.
Os desenvolvedores frequentemente usavam soluções alternativas para obter flexibilidade similar. Por exemplo, considere um cenário em que o código autogerado precisa chamar alguma lógica personalizada durante a construção do objeto. Sem construtores parciais, um padrão comum era usar um método parcial dentro do construtor. A parte autogerada de uma classe podia chamar esse método, enquanto a implementação do método ficava em outro arquivo. Por exemplo, em código C# antigo você podia ver:
// Before C# 14: using a partial method to extend constructor logic
public partial class Car
{
public Car()
{
InitializeComponents(); // auto-generated initialization
OnConstructed(); // call a partial method hook
}
// This partial method can be implemented in another file to add custom behavior
partial void OnConstructed();
}
No segundo arquivo, o desenvolvedor podia implementar o método parcial OnConstructed para executar código adicional após InitializeComponents(). Esse padrão permitia injetar código personalizado no momento da construção, mas era indireto e um tanto desajeitado. Para eventos, não havia uma solução alternativa fácil; era preciso herdar e sobrescrever a funcionalidade ou modificar o código gerado, já que não dava para dividir a lógica de add/remove de um evento entre arquivos.
Com os construtores e eventos parciais do C# 14, essas soluções alternativas não são mais necessárias. Você pode declarar diretamente um construtor ou evento como parcial e fornecer a implementação separadamente, deixando o código mais direto. A nova abordagem é mais clara e elimina a necessidade de métodos parciais fictícios ou outros truques.
Casos de uso e benefícios
Os principais beneficiários dos construtores e eventos parciais são cenários envolvendo geração de código e ferramentas. Muitos frameworks e ferramentas geram código C# (por exemplo, designers de UI, ORMs ou ferramentas de scaffolding de interfaces) e frequentemente marcam classes como parciais para que desenvolvedores possam estender as classes geradas sem editar o código autogerado. Com o C# 14:
- Geradores de código podem definir construtores para você: por exemplo, um source generator pode criar uma assinatura de construtor parcial que o desenvolvedor precisa implementar com lógica de inicialização personalizada. Inversamente, um gerador pode implementar um construtor parcial que o desenvolvedor declarou, talvez para injetar código de configuração específico do framework nos bastidores. Isso facilita misturar inicialização gerada com inicialização escrita pelo usuário de forma segura.
- Dividir lógica de construção complexa: em projetos grandes, você pode querer organizar a lógica do construtor em vários arquivos (por exemplo, separando a fiação de dependências da lógica de negócio). Construtores parciais oferecem uma forma estruturada de fazer isso, se necessário.
- Padrões de evento personalizados: eventos parciais habilitam cenários avançados de tratamento de eventos. Um exemplo notável é o padrão de evento fraco (para evitar vazamentos de memória com eventos). Um desenvolvedor ou source generator pode declarar um evento parcial em um arquivo (talvez anotado com algo como
[WeakEvent]) e implementá-lo em outro arquivo de forma queadd/removeusem referências fracas para os manipuladores. Isso significa que os objetos assinantes não impedem a coleta de lixo se esquecerem de cancelar a assinatura. Antes dos eventos parciais, implementar um padrão de evento fraco podia exigir muito boilerplate ou bibliotecas externas. Agora um autor de biblioteca pode fornecer um gerador que automaticamente fornece a lógica complexa de add/remove em uma implementação parcial separada, enquanto o código do usuário permanece limpo. Da mesma forma, eventos parciais podem ser usados para encaminhar assinaturas de eventos para sistemas subjacentes (por exemplo, encapsulando um evento de uma API de baixo nível ao tratar add/remove e conectá-los à fonte real). - Código de plataforma e interop: em cenários de interoperabilidade (como Xamarin ou interop do .NET com bibliotecas nativas), classes geradas frequentemente precisam chamar código nativo nos construtores ou gerenciar conexões de eventos nativos. Construtores parciais permitem que a configuração da chamada nativa fique no código gerado, enquanto o restante do construtor é definido pelo usuário (ou vice-versa). Eventos parciais podem permitir que uma classe exponha um evento .NET que, na verdade, é respaldado por algum mecanismo de eventos específico da plataforma implementado em outro arquivo parcial.
Em todos esses casos, construtores e eventos parciais tornam o código mais fácil de manter. O código autogerado pode declarar o que precisa existir, e o código escrito à mão pode se limitar ao que o desenvolvedor realmente quer implementar. Também não há custo de desempenho em tempo de execução por usar membros parciais; a separação existe apenas em tempo de compilação. O programa resultante se comporta como se você tivesse escrito o construtor ou o evento completo em um único lugar.
Ao permitir que construtores de instância e eventos sejam partial, o C# 14 completa o conjunto de recursos de classes parciais para cobrir quase todos os tipos de membros. Esse aprimoramento melhora a capacidade da linguagem de oferecer suporte a uma separação limpa entre código autogerado e código personalizado. Os desenvolvedores ganham flexibilidade para organizar e estender o comportamento das classes sem gambiarras, e os source generators ganham uma nova ferramenta poderosa para injetar funcionalidade de forma modular. Quer você esteja trabalhando em frameworks grandes ou apenas dividindo código por clareza, construtores e eventos parciais oferecem uma abordagem mais expressiva e conveniente no C# 14.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.