C# - Apresentando Partial Class


 Neste artigo vou apresentar o conceito de Partial Class na linguagem C#.

Na linguagem C# , quando você cria uma classe concreta você a define usando a seguinte sintaxe:

<modificador de acesso> class  <nome da classe>
{
       <construtor>
       <campos>
       <propriedades>
       <métodos>
       <eventos>
       <delegates>
       <indexadores>
       <classe aninhadas>
}

De forma resumida uma classe é definida pela palavra reservada class seguida de seu nome e o corpo da classe é definido dentro das chaves {....}  onde definimos os membros da classe.

Exemplo:

public class  MinhaClasse
{
       <membros>
}

Quando definimos uma classe usando essa sintaxe, temos que definir em um único arquivo físico com extensão .cs, todos os membros da classe. Dessa forma não podemos declarar uma classe em dois arquivos separados em um mesmo projeto.

A partir da versão 2.0 a plataforma .NET introduziu o recurso chamado  partial class que permite ter uma única classe ser implementada em múltiplos arquivos físicos com extensão .cs.

Desta forma é possível dividir a definição de uma classe, struct ou interface ou um 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.

Isso é feito usando o modificador partial que pode ser aplicado em uma classe, método, interface e struct.

Usando o conceito de partial class você pode dividir a definição de sua classe em mais de um arquivo físico.

Logicamente não há diferença alguma para o compilador pois quando uma partial class é compilada o compilador agrupa todos os arquivos da classe parcial gerando uma única entidade.(O código da linguagem intermediária formado é o mesmo).

Assim vamos criar a classe parcial MinhaClasseParcial em dois arquivos físicos diferentes:

MinhaClassePropriedades.cs

public partial class MinhaClasseParcial
{
     public DateTime DataNascimento {  get;  set; }
     public string Nome { get; set; }
}

MinhaClasseMetodos.cs

public partial class  MinhaClasseParcial
{
     public TimeSpan CalculaIdade(DateTime DataNascimento)  
     {
        return (DateTime.Now.Date - DataNascimento);
     }
}

Acima temos a definição de uma partial class em dois arquivos físicos distintos:

  1. minhaClassePropriedades.cs - Contém as propriedades da classe parcial MinhaClasseParcial;
  2. minhaClasseMetodos.cs - Contém os métodos da classe parcial MinhaClasseParcial;

Após a compilação será gerada a classe MinhaClasseParcial:

public class MinhaClasseParcial
{
     public DateTime DataNascimento {  get;  set; }
     public string Nome { get; set; }

     public TimeSpan CalculaIdade(DateTime DataNascimento) 
     {
        return (DateTime.Now.Date - DataNascimento);
     }
}

A seguir outros exemplos do uso do modificador partial

1. Separar a implementação de uma interface

public interface IMinhaInterface
{
  
void Metodo1();
}

public partial class MinhaClasse : IMinhaInterface
{
  
public void Metodo1()
   {
      Console.WriteLine(
"Método1");
   }
}

public partial class MinhaClasse
{
  
public void Metodo2()
   {
      Console.WriteLine(
"Método2");
   }
}

Neste exemplo, a interface IMinhaInterface é implementada pela classe MinhaClasse. A implementação do método Metodo1 é definida em uma parte da classe MinhaClasse, enquanto a implementação do método Metodo2 é definida em outra parte. Dessa forma, a implementação da interface é separada da implementação de outros membros da classe.

2. Separar a definição de propriedades

public partial class MinhaClasse
{
  
private int _idade;
  
public int Idade
   {
   
 get { return _idade; }
    
set { _idade = value; }
   }
}

public partial class MinhaClasse
{
  
public string Nome { get; set; }
}

Neste exemplo, a classe MinhaClasse define duas propriedades, Idade e Nome, que são definidas em partes diferentes da classe. A propriedade Idade tem um getter e um setter personalizados, enquanto a propriedade Nome usa a sintaxe simplificada para definir um getter e um setter.

3. Separar a definição de eventos

public partial class MinhaClasse
{
  
public event EventHandler MeuEvento;
  
private void RaiseMeuEvento()
   {
      MeuEvento?.Invoke(
this, EventArgs.Empty);
   }
}

public partial class MinhaClasse
{
  
public void FazAlgo()
   {
    
// faz algo aqui
     RaiseMeuEvento();
   }
}

Neste exemplo, a classe MinhaClasse define um evento MeuEvento que é disparado pelo método RaiseMeuEvento. O método FazAlgo() é definido em outra parte da classe e usa o método RaiseMeuEvento para disparar o evento. Dessa forma, a definição do evento é separada da implementação do método que o usa.

Vantagens e regras de uso

Uma das vantagens em se utilizar as Partial Class é poder separar a lógica de negócio da interface de usuário em arquivos distintos facilitando a manutenção e o trabalho em equipe. (Será..??)

Algumas regras que devemos ter mente ao usar as classes parciais:

  1. Todas as definições das classes parciais devem estar no mesmo assembly e no mesmo namespace;
  2. Todas as partes devem possui a mesma acessabilidade como public ou private, etc;
  3. Se qualquer parte for declarada abstract, sealed ou base type então toda a classe será declarada do mesmo tipo;
  4. O modificador partial somente pode ser usado antes da palavra-chave class, struct ou interface;
  5. Tipos parciais aninhados são permitidos;
  6. Os atributos se aplicam a todas as partes da classe;
  7. As caracteristicas definidas em qualquer parte estão disponíveis para todas a partes da classes;

A seguir vemos exemplos que ilustram a aplicação destas regras a partir da terceira regra:

3. Se qualquer parte for declarada abstract, sealed ou base type então toda a classe será declarada do mesmo tipo

public partial class MinhaClasse
{
  
// código da primeira parte da classe
}

public partial class MinhaClasse
{
 
// código da segunda parte da classe
  // declarando um método abstrato na segunda parte
 
public abstract void MeuMetodo();
}

Neste exemplo, a primeira parte da classe MinhaClasse não tem modificador de acesso e não define nenhum membro. Já a segunda parte define um método abstrato MeuMetodo().

Como o método é abstrato, a classe inteira será considerada como abstrata.

4. O modificador partial somente pode ser usado antes da palavra-chave class, struct ou interface

// erro de compilação
partial
enum MeuEnum
{
  
// código da primeira parte do enum
}

public partial class MinhaClasse
{
  
// código da primeira parte da classe
}

// erro de compilação
partial
delegate void MeuDelegate();

 

Neste exemplo, há um erro de compilação ao tentar usar partial com uma definição de enum ou delegate. Isso acontece porque o modificador partial só pode ser usado antes de class, struct ou interface.

5. Tipos parciais aninhados são permitidos

public partial class MinhaClasse
{
  
// código da primeira parte da classe
  
public partial class MinhaClasseAninhada
   {
     
// código da primeira parte da classe aninhada
   }
}

public partial class MinhaClasse
{
  
// código da segunda parte da classe
  
public partial class MinhaClasseAninhada
   {
     
// código da segunda parte da classe aninhada
   }
}

Neste exemplo, a classe MnhaClasse tem uma classe aninhada MinhaClasseAninhada. A definição da classe aninhada é dividida em duas partes separadas. A primeira parte é definida na primeira parte da classe MinhaClasse, enquanto a segunda parte é definida na segunda parte da classe.

6. Os atributos se aplicam a todas as partes da classe

[Serializable]
public
partial class MinhaClasse
{
 
// código da primeira parte da classe
}

public partial class MinhaClasse
{
 
// código da segunda parte da classe
}

Neste exemplo, a classe MinhaClasse tem o atributo [Serializable] aplicado a ela na primeira parte da classe. Como o atributo se aplica a toda a classe, a segunda parte da classe também será considerada serializável.

7.As características definidas em qualquer parte estão disponíveis para todas as partes das classes;

public partial class MinhaClasse
{
  
private int _meuInt;

  
public void SetMeuInt(int valor)
   {
     _meuInt = valor;
   }
}

public partial class MinhaClasse
{
  
public void ImprimirMeuInt()
   {
     Console.WriteLine(_meuInt);
   }
}

Neste exemplo, a classe MinhaClasse é definida em duas partes. A primeira parte define uma variável privada _meuInt e um método SetMeuInt para configurar o valor da variável. A segunda parte define um método ImprimirMeuInt que imprime o valor da variável _meuInt.

Observe que a variável _meuInt é definida na primeira parte da classe, mas é usada na segunda parte da classe no método ImprimirMeuInt . Isso é possível porque as características definidas em qualquer parte estão disponíveis para todas as partes da classe. Dessa forma, a segunda parte da classe pode acessar a variável _meuInt definida na primeira parte da classe sem nenhum problema.

Na figura abaixo vemos que a instância da classe parcial MinhaClasseParcial tem acesso a todos os membros definidos em cada parte:

Utilização das classes parciais e boas práticas

Existem várias situações em que a divisão de uma definição de classe é desejável:

Assim, as classes parciais são geralmente usadas com códigos gerados por alguma ferramenta, de forma a permitir que o programador injete código dentro dessa classe gerada, sem ter de alterar o arquivo de código gerado.

Usar classes parciais em um código não gerado, faz com que o código fique confuso.

Um dos princípios SOLID , o princípio SRP,  reza que uma mesma classe de não deve ter diversas funções.

Dessa forma, não estaria a utilização do recurso partial class violando esse princípio SOLID, quando usado apenas para esse propósito ?

Use, quando estritamente necessário, e não abuse.

E estamos conversados...

"Portanto, agora nenhuma condenação há para os que estão em Cristo Jesus, que não andam segundo a carne, mas segundo o Espírito."
Romanos 8:1

Referências:


José Carlos Macoratti