C# - Obsessão por primitivos (revisitado)
Hoje vamos retornar ao assunto obsessão por primitivos recordando este importante conceito. |
O termo 'obsessão por primitivos' na linguagem C# refere-se à preferência por tipos de dados primitivos em detrimento de tipos de dados mais complexos ou personalizados.
Na linguagem C#, os tipos de dados primitivos incluem tipos numéricos (como
int, float e double), tipos booleanos (bool) e tipos de caracteres (char).
Esses tipos são considerados primitivos porque eles são tipos de dados básicos
que são implementados diretamente pelo compilador e pela máquina virtual
sem a necessidade de bibliotecas ou código adicional.
Embora a linguagem C# permita a criação de tipos de dados personalizados (como
classes e estruturas), a obsessão por primitivos defende que, sempre que
possível, é melhor usar os tipos primitivos, devido à sua eficiência e
simplicidade.
Essa abordagem pode ser útil em cenários de alto desempenho, onde o tempo de
execução é um fator crítico. No entanto, em outros casos, pode ser mais
apropriado usar tipos de dados personalizados para melhorar a legibilidade,
modularidade e manutenção do código.
Um exemplo de código que evidencia a obsessão por primitivos na linguagem C#
seria a utilização de tipos numéricos primitivos em vez de tipos personalizados
para representar unidades de medida.
Por exemplo, em vez de criar uma classe "Tempo" com
propriedades "Horas", "Minutos" e "Segundos", um
programador que segue a obsessão por primitivos poderia optar por usar
apenas um tipo numérico para representar o tempo total em segundos:
int tempoTotalSegundos = 3600; // representa 1 hora |
Embora essa abordagem seja mais simples e direta, ela pode tornar o código menos legível e mais suscetível a erros. Além disso, ela não permite a inclusão de informações adicionais, como a data em que o tempo foi registrado, por exemplo.
Nesse caso, seria mais apropriado criar uma classe personalizada para representar o tempo, como no exemplo a seguir:
public
class
Tempo { public int Horas { get; set; } public int Minutos { get; set; } public int Segundos { get; set; } public Tempo(int horas, int minutos, int segundos) { Horas = horas; Minutos = minutos; Segundos = segundos; } public override string ToString() { return $"{Horas}h {Minutos}m {Segundos}s"; } } |
O código a seguir permite definir um tempo e realizar a exibição no console:
Tempo tempoRegistrado = new Tempo(1, 0, 0); Console.WriteLine(tempoRegistrado.ToString()); |
Resultado:
Nesse exemplo, a classe "Tempo" permite a inclusão de informações adicionais e torna o código mais legível e fácil de entender. Embora seja um pouco mais complexo do que o exemplo anterior, essa abordagem é mais flexível e pode ser mais adequada em muitos casos.
Vejamos outro exemplo onde é muito comum o uso de tipos primitivos.
A seguir temos o código da classe Endereco onde temos o uso de tipos primitivos para representar o Cep, o Local e o Pais :
public
class
Endereco { public string Cep { get; set; } public string Local { get; set; } public string Pais { get; set; } public void ValidarCep(string cep) {} } |
Para refatorar a classe Endereco de forma a não usar primitivos para representações inadequadas, podemos usar tipos personalizados para representar informações mais complexas e específicas.
Por exemplo, podemos criar uma classe CEP para representar o CEP do endereço, em vez de usar uma string genérica:
public class CEP
{
public string Numero { get; private set; }
public CEP(string numero)
{
Validar(numero);
Numero = numero;
}
private void Validar(string numero)
{
if (string.IsNullOrEmpty(numero) || numero.Length != 8 || !int.TryParse(numero, out int _))
{
throw new ArgumentException("CEP inválido.");
}
}
public override string ToString()
{
return Numero;
}
}
|
Com isso podemos ajustar o código da classe Endereco usando o tipo CEP definido no lugar da string:
public
class
Endereco { public CEP Cep { get; set; } public string Local { get; set; } public string Pais { get; set; } } |
Nesse exemplo, a
classe CEP encapsula a lógica de validação do CEP e impede a criação de
instâncias inválidas. Além disso, ela pode ser facilmente reutilizada em outras
partes do código, caso necessário.
Também podemos usar tipos personalizados para representar outras informações do
endereço, como o país e o local, se necessário. Por exemplo:
1- Pais
public class Pais
{
public string Nome { get; private set; }
public Pais(string nome)
{
Validar(nome);
Nome = nome;
}
private void Validar(string nome)
{
if (string.IsNullOrEmpty(nome))
{
throw new ArgumentException("Nome do país inválido.");
}
}
public override string ToString()
{
return Nome;
}
}
|
2- Local
public class Local
{
public string Nome { get; private set; }
public Local(string nome)
{
Validar(nome);
Nome = nome;
}
private void Validar(string nome)
{
if (string.IsNullOrEmpty(nome))
{
throw new ArgumentException("Nome do local inválido.");
}
}
public override string ToString()
{
return Nome;
}
}
|
Podemos agora definir a classe Endereco da seguinte forma :
public class Endereco
{
public CEP Cep;
public Local Local;
public Pais Pais;
public Endereco(CEP cep, Local local, Pais pais)
{
Cep = cep;
Local = local;
Pais = pais;
}
public override string ToString()
{
return $"CEP: {Cep.Numero}, Local: {Local.Nome}, País: {Pais.Nome}";
}
}
|
Agora a classe Endereco usa as classes CEP, Loal e Pais que encapsulam a validação e a representação de informações específicas do endereço, tornando o código mais legível e coeso.
A seguir vamos criar dois endereços e exibir no console:
// Exemplo de
criação de objetos Endereco var cep1 = new CEP("12345678"); var local1 = new Local("Rua A, 123"); var pais1 = new Pais("Brasil"); var endereco1 = new Endereco(cep1, local1, pais1);var cep2 = new CEP("54321876");var local2 = new Local("Rua B, 456"); var pais2 = new Pais("Estados Unidos"); var endereco2 = new Endereco(cep2, local2, pais2);// Exibindo os objetos criados Console.WriteLine(endereco1); Console.WriteLine(endereco2); |
Desta forma podemos concluir que quando utilizamos apenas tipos primitivos, temos algumas desvantagens, como:
Por outro lado, quando utilizamos classes e tipos personalizados para representar valores e objetos, temos algumas vantagens, como:
Portanto, utilizar classes e tipos personalizados em vez de tipos primitivos pode trazer diversas vantagens em relação à legibilidade, reusabilidade e evolução do código em C#.
Naturalmente você deve usar o bom senso e considerar o cenário e o contexto do seu código para decidir quando usar ou não tipos primitivos e quais os benefícios que isso pode te trazer.
E estamos conversados ...
"Há um caminho que ao homem parece direito, mas o fim dele são os caminhos da
morte."
Provérbios 14:12
Referências:
LINQ - Gerando um Produto Cartesiano - Macoratti
C# - Salvando e Lendo informações em um arquivo XML - Macoratti