Implementar classes usando classes parciais
É possível dividir a definição de uma classe ou método em dois ou mais arquivos de origem. Cada arquivo de origem contém uma seção da definição de tipo ou método e todas as partes são combinadas quando o aplicativo é compilado.
Classes parciais
Há várias situações em que a divisão de uma definição de classe é desejável:
- Declarar uma classe em arquivos separados permite que vários programadores trabalhem nela ao mesmo tempo.
- Você pode adicionar código à classe sem precisar recriar o arquivo de origem que inclui a origem gerada automaticamente. O Visual Studio usa essa abordagem quando cria o Windows Forms, o código de wrapper do serviço Web e assim por diante. Você pode criar um código que usa essas classes sem precisar modificar o arquivo criado pelo Visual Studio.
- Os geradores de origem podem gerar funcionalidade extra em uma classe.
Para dividir uma definição de classe, use o modificador de palavra-chave parcial. Na prática, cada classe parcial normalmente é definida em um arquivo separado, facilitando o gerenciamento e a expansão da classe ao longo do tempo.
O exemplo de Employee a seguir demonstra como a classe pode ser dividida entre dois arquivos: Employee_Part1.cs e Employee_Part2.cs.
// This is in Employee_Part1.cs
public partial class Employee
{
public void DoWork()
{
Console.WriteLine("Employee is working.");
}
}
// This is in Employee_Part2.cs
public partial class Employee
{
public void GoToLunch()
{
Console.WriteLine("Employee is at lunch.");
}
}
//Main program demonstrating the Employee class usage
public class Program
{
public static void Main()
{
Employee emp = new Employee();
emp.DoWork();
emp.GoToLunch();
}
}
// Expected Output:
// Employee is working.
// Employee is at lunch.
A palavra-chave partial indica que outras partes da classe podem ser definidas no namespace. Todas as partes devem usar a palavra-chave partial. Todas as partes devem estar disponíveis no tempo de compilação para formar o tipo final. Todas as partes devem ter a mesma acessibilidade, como public, privatee assim por diante.
Se qualquer parte for declarada abstrata, o tipo inteiro será considerado abstrato. Se qualquer parte for declarada lacrada, o tipo inteiro será considerado lacrado. Se qualquer parte declarar um tipo base, o tipo inteiro herdará essa classe.
Todas as partes que especificam uma classe base devem concordar, mas partes que omitem uma classe base ainda herdam o tipo base. As partes podem especificar diferentes interfaces base e o tipo final implementa todas as interfaces listadas por todas as declarações parciais. Todos os membros de classe, struct ou interface declarados em uma definição parcial estão disponíveis para todas as outras partes. O tipo final é a combinação de todas as partes em tempo de compilação.
Nota
O modificador de partial não está disponível em declarações de representante ou enumeração.
O exemplo a seguir mostra que os tipos aninhados podem ser parciais, mesmo que o tipo em que estão aninhados não seja parcial.
class Container
{
partial class Nested
{
void Test() { }
}
partial class Nested
{
void Test2() { }
}
}
No momento da compilação, os atributos de definições de tipo parcial são mesclados. Por exemplo, considere as seguintes declarações:
[SerializableAttribute]
partial class Moon { }
[ObsoleteAttribute]
partial class Moon { }
Elas são equivalentes às seguintes declarações:
[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }
As seguintes são mescladas de todas as definições de tipo parcial:
- Comentários XML. No entanto, se ambas as declarações de um membro parcial incluirem comentários, somente os comentários do membro em implementação serão incluídos.
- Interfaces
- atributos de parâmetro de tipo genérico
- atributos de classe
- Membros
Por exemplo, considere as seguintes declarações:
partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }
Elas são equivalentes às seguintes declarações:
class Earth : Planet, IRotate, IRevolve { }
Restrições em definições parciais de classe
Há várias regras a seguir quando você está trabalhando com definições parciais de classe:
Todas as definições de tipo parcial destinadas a serem partes do mesmo tipo devem ser modificadas com parcial. Por exemplo, as seguintes declarações de classe geram um erro:
public partial class A { } //public class A { } // Error, must also be marked partialO modificador parcial só pode aparecer imediatamente antes da classe de palavra-chave, struct ou interface.
Tipos parciais aninhados são permitidos em definições de tipo parcial, conforme ilustrado no exemplo a seguir:
partial class ClassWithNestedClass { partial class NestedClass { } } partial class ClassWithNestedClass { partial class NestedClass { } }Todas as definições de tipo parcial destinadas a serem partes do mesmo tipo devem ser definidas no mesmo assembly e no mesmo módulo (.exe ou .dll arquivo). Definições parciais não podem abranger vários módulos.
O nome da classe e os parâmetros de tipo genérico devem corresponder a todas as definições de tipo parcial. Tipos genéricos podem ser parciais. Cada declaração parcial deve usar os mesmos nomes de parâmetro na mesma ordem.
As seguintes palavras-chave em uma definição de tipo parcial são opcionais, mas se estiverem presentes em uma definição de tipo parcial, o mesmo deverá ser especificado em outra definição parcial para o mesmo tipo:
- público
- particular
- Protegido
- interno
- abstrair
- selado
- classe base
- novo modificador (partes aninhadas)
- restrições genéricas
Implementar classes parciais
No exemplo a seguir, os campos e o construtor da classe Coords são declarados em uma definição de classe parcial (Coords_Part1.cs) e o método PrintCoords é declarado em outra definição de classe parcial (Coords_Part2.cs). Essa separação demonstra como as classes parciais podem ser divididas em vários arquivos para facilitar a manutenção.
// This is in Coords_Part1.cs
public partial class Coords
{
private int x;
private int y;
public Coords(int x, int y)
{
this.x = x;
this.y = y;
}
}
// This is in Coords_Part2.cs
public partial class Coords
{
public void PrintCoords()
{
Console.WriteLine("Coords: {0},{1}", x, y);
}
}
// Main program demonstrating the Coords class usage
class TestCoords
{
static void Main()
{
Coords myCoords = new Coords(10, 15);
myCoords.PrintCoords();
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output: Coords: 10,15
Membros parciais
Uma classe parcial pode conter um membro parcial. Uma parte da classe contém a assinatura do membro. Uma implementação pode ser definida na mesma parte ou em outra parte.
Uma implementação não é necessária para um método parcial quando a assinatura obedece às seguintes regras:
A declaração não inclui modificadores de acesso. O método tem acesso privado por padrão.
O tipo de retorno é
void.Nenhum dos parâmetros tem o modificador
out.A declaração do método não pode incluir nenhum dos seguintes modificadores:
- virtual
- substituição
- selado
- Novo
- Extern
O método e todas as chamadas para o método são removidos no momento da compilação quando não há nenhuma implementação.
Qualquer método que não esteja em conformidade com todas essas restrições, incluindo propriedades e indexadores, deve fornecer uma implementação. Essa implementação pode ser fornecida por um gerador de origem. Propriedades parciais não podem ser implementadas usando propriedades implementadas automaticamente. O compilador não pode distinguir entre uma propriedade implementada automaticamente e a declaração de declaração de uma propriedade parcial.
A partir do C# 13, a declaração de implementação de uma propriedade parcial pode usar propriedades com suporte de campo para definir a declaração de implementação. Uma propriedade com suporte de campo fornece uma sintaxe concisa em que a palavra-chave do campo acessa o campo de backup sintetizado do compilador para a propriedade. Por exemplo, você pode escrever o seguinte código:
// in file1.cs
public partial class PropertyBag
{
// Defining declaration
public partial int MyProperty { get; set; }
}
// In file2.cs
public partial class PropertyBag
{
// Defining declaration
public partial int MyProperty { get => field; set; }
}
Você pode usar field no acessador get ou set ou ambos.
Importante
A palavra-chave field é um recurso de visualização no C# 13. Você deve estar usando o .NET 9 e definir seu elemento <LangVersion> para visualizar no arquivo de projeto para usar a palavra-chave contextual field.
Você deve ter cuidado ao usar o recurso de palavra-chave field em uma classe que tenha um campo chamado field. A nova palavra-chave field sombreia um campo chamado field no escopo de um acessador de propriedade. Você pode alterar o nome da variável de campo ou usar o token @ para referenciar o identificador de campo como @field.
Métodos parciais permitem que o implementador de uma parte de uma classe declare um membro. O implementador de outra parte da classe pode definir esse membro. Há dois cenários em que essa separação é útil: modelos que geram código clichê e geradores de origem.
Código do modelo: o modelo reserva um nome de método e uma assinatura para que o código gerado possa chamar o método. Esses métodos seguem as restrições que permitem que um desenvolvedor decida se deseja implementar o método. Se o método não for implementado, o compilador removerá a assinatura do método e todas as chamadas para o método. As chamadas para o método, incluindo quaisquer resultados que ocorram a partir da avaliação de argumentos nas chamadas, não têm efeito em tempo de execução. Portanto, qualquer código na classe parcial pode usar livremente um método parcial, mesmo que a implementação não seja fornecida. Nenhum resultado de erros em tempo de compilação ou em tempo de execução se o método for chamado, mas não implementado.
Geradores de origem: os geradores de origem fornecem uma implementação para os membros. O desenvolvedor humano pode adicionar a declaração de membro (geralmente com atributos lidos pelo gerador de origem). O desenvolvedor pode escrever código que chama esses membros. O gerador de origem é executado durante a compilação e fornece a implementação. Nesse cenário, as restrições para membros parciais que podem não ser implementados geralmente não são seguidas.
// Definition in file1.cs partial void OnNameChanged(); // Implementation in file2.cs partial void OnNameChanged() { // method body }As declarações de membro parcial devem começar com a palavra-chave contextual parcial.
As assinaturas parciais de membro em ambas as partes do tipo parcial devem corresponder.
O membro parcial pode ter modificadores estáticos e não seguros.
O membro parcial pode ser genérico. As restrições devem ser as mesmas na declaração de método de definição e implementação. Os nomes de parâmetros de parâmetro e de tipo não precisam ser os mesmos na declaração de implementação como na definição.
Você pode fazer um delegado para um método parcial definido e implementado, mas não para um método parcial que não tenha uma implementação.