A expressividade do C#
03 Jan 2018Faz 2 anos que estou trabalhando com C# e neste tempo aprendi muita coisa sobre a linguagem. Mas o que mais me chama a atenção nesta linguagem é a sua expressividade.
Neste post vou mostrar quais pontos do C# mais me chamam a atenção e como você também pode melhorar seu código e torná-lo mais expressivo com a linguagem.
NOTA: Este texto tem como objetivo mostrar alguns recursos da linguagem C# isoladamente. Não tenho a intenção de comparar linguagens.
Tipos implícitos
No C#, existe uma palavra reservada chamada var. Ela te permite declarar variáveis com o tipo implícito, ou seja, o compilador determina o tipo dela para você. Mas atenção que o C# ainda é fortemente tipado, portanto uma variável declarada com o tipo int
sempre será int
.
Mas como isso pode aumentar a expressividade? Bom, primeiro que podemos economizar a escrita de alguns tipos e segundo, que entra um pouco na parte de manutenção, é que, por exemplo, a mudança do tipo de retorno de um método pode ser menos dolorosa.
//Implícito
var lista = new List<int>(); //Sempre List<int>
var clientes = ListaClientes();
//Explícito
List<int> lista = new List<int>();
ICollection<Cliente> clientes = ListaClientes();
Reduzimos a quantidade de código "desnecessário" para podermos focar no que interessa.
Propriedades
Em uma classe, geralmente temos propriedades e ações. O C# te permite explícitamente separar os dois assuntos:
class Cachorro
{
public string CorPele { get; set; }
public void Latir()
{
//TODO late rapaz!
}
}
E o acesso às propriedades ficam muito mais explícitos:
var cachorro = new Cachorro();
cachorro.CorPele = "Marrom";
cachorro.Latir();
Validações
No exemplo anterior, criei uma propriedade auto-implemented, mas podemos criar validações:
class Cachorro
{
private string corPele;
public strin CorPele
{
get { return corPele; }
set
{
if (value == null)
throw new InvalidOperationException("A cor da pele não pode ser nula");
corPele = value;
}
}
}
Controlando leitura e escrita
É possível controlar se uma propriedade é somente get
(leitura) ou somente set
(escrita).
class Cachorro
{
public int Anos { get; } //Somente leitura
public String CorPele { set; } //Somente escrita
}
Tratando dados
Também podemos criar algum tratamento:
class Cachorro
{
public int Anos { get; set; }
public int AnosDeCachorro
{
get { return anos * 7; }
}
}
Expressões
A partir do C# 6, é possível criar propriedades somente leitura com expressões. Isto elimina aquele monte de chaves para criar um único get
. Veja o exemplo anterior reescrito:
class Cachorro
{
public int Anos { get; set; }
public int AnosDeCachorro => Anos * 7; //Somente leitura
}
LINQ
Geralmente trabalhamos com listas de dados, certo? E aplicar operações nestas listas nos levam a escrever mais código, como filtrar números inteiros maiores do que 10.
O LINQ está aí para nos ajudar, que é o acrônimo de Language-Integrated Query.
Não vou me alongar sobre LINQ aqui pois é um assunto bem extenso, portanto recomendo a leitura da documentação para mais exemplos. Mas para mostrar a melhoria na expressividade do código com o LINQ, fica o exemplo do filtro de inteiros maiores do que 10:
int[] numeros = new int[] { 1, 10, 34, 3, 43};
//Sem LINQ
var numerosFiltrados = new List<int>();
foreach (int numero in numeros)
{
if (numero > 10)
numerosFiltrados.add(numero);
}
//Com LINQ
var numerosFiltrados = numeros.Where(numero => numero > 10).ToList();
Métodos de extensão
Já pensou em extender o comportamento de alguma classe? Ter aquele método adicional que facilitaria tudo? Então fique contente pois o C# permite tal ação com métodos de extensão.
Para este caso, vou dar um exemplo real. Em certa situação, eu tinha uma coleção de veículos e precisava separá-los em veículos de tração e reboque, também existiam outros locais do código onde era necessário a mesma operação.
(Os exemplos foram simplificados)
class Veiculo
{
public String Placa { get; set; }
public bool Tracao { get; set; }
}
Eu poderia ter feito algo assim:
static class VeiculoUtil
{
public static FiltrarTracoes(IEnumerable<Veiculo> veiculos)
{
return veiculos.Where(veiculo => veiculo.Tracao);
}
}
Mas o uso ficaria "esquisito":
var veiculos = ListarVeiculos();
var tracoes = VeiculoUtil.FiltrarTracoes(veiculos);
Então fiz um método de extensão:
static class VeiculoExtensions
{
public static Tracoes(this IEnumerable<Veiculo> veiculos)
{
return veiculos.Where(veiculo => veiculo.Tracao);
}
}
E o código ficou muito mais expressivo:
var veiculos = ListarVeiculos();
var tracoes = veiculos.Tracoes();
Com métodos de extensão, dá a noção de que o método faz parte do objeto (mesmo tecnicamente não sendo). Combinando isto com o LINQ, você pode até criar seus próprios operadores (que também aconteceu onde atualmente trabalho).
Funções e ações
Existem dois delegates muito interessantes no C#: Func e Action.
Func
Como o tipo Func representa uma função com retorno, podemos usar lambdas e referenciar métodos:
public int Multiplicar(Func<int> fnValor)
{
return fnValor * 2;
}
public int ValorMagico()
{
return 23;
}
//Com lambda
int resultado = Multiplicar(() => 2);
//Referenciando método
int resultado = Multiplicar(ValorMagico);
Em alguns casos mais simples, podemos evitar o a criação de herança e usar o tipo Func para permitir uma flexibilidade em alguns pontos do código.
Action
O tipo Action representa uma função sem retorno. Veja um exemplo real que encaixou muito bem, onde desejávamos simplificar o uso das transações com o banco de dados:
public void ComTransacao(Action acao)
{
try
{
BeginTransation();
acao();
CommitTransation();
}
catch (Exception ex)
{
RollbackTransaction();
throw ex;
}
}
O código acima encapsula um trecho de código em uma transação, agora estamos livres dos vários BeginTransation(), CommitTransation() e RollbackTransaction() em muitos lugares no projeto. O uso fica assim:
db.ComTransacao(() =>
{
//Operações no banco
});
Parâmetros nomeados
Provavelmente você já passou pela situação de não querer deixar aquele número largado em um parâmetro sem qualquer indicador sobre o assunto do número, certo? Talvez você tenha criado uma variável só para poder dar um nome legal, ou até uma constante, mas o C# te ajuda a evitar esta situação com parâmetros nomeados. Veja a classe abaixo:
class Teste
{
public void Metodo(string parametro, int numero)
{
//TODO ...
}
}
Veja como podemos usar este método de uma forma bem interessante:
//Assim não né?
var teste = new Teste();
teste.Metodo("texto", 2);
//Que tal assim?
var teste = new Teste();
teste.Metodo("texto", numero: 2);
Isto serve para construtores também:
var produto = new Produto(
id: 4,
descricao: "Chinelo",
preco: 14.5
)
Conversão implícita
Já pensou em poder criar um tipo sem precisar ficar dando new
toda vez? Pois é, com conversão implícita podemos melhorar o uso de alguns tipos. Repare na classe Cpf
abaixo.
class Cpf
{
public string Numero { get; private set; }
public Cpf(string numero)
{
Numero = numero;
}
}
Como ele tem um único parâmetro no construtor, podemos declarar uma conversão implícita de string
para Cpf
dentro da classe:
public static implicit operator Cpf(string numero)
{
return new Cpf(numero);
}
Então o uso fica bem simplificado.
Cpf cpf = "00000000000";
Sobrescrita de operadores
Em alguns momentos, os tipos criados por nós podem precisar sofrer alguma operação matemática, ou simplesmente queremos usar o operador de igualdade e não encher nosso código de chamadas para Equals
.
Usando a classe Cpf
, podemos sobrescrever os operadores de igualdade e desigualdade.
class Cpf
{
public string Numero { get; private set; }
public Cpf(string numero)
{
Numero = numero;
}
public static bool operator ==(Cpf esquerda, Cpf direita)
{
return esquerda.Numero == direita.Numero;
}
public static bool operator !=(Cpf esquerda, Cpf direita)
{
return esquerda.Numero != direita.Numero;
}
}
Então podemos compará-los com ==
normalmente.
Cpf cpf1 = new Cpf("00000000000");
Cpf cpf2 = new Cpf("11111111111");
bool iguais = cpf1 == cpf2;
Conclusão
O C# é uma linguagem muito interessante e em constante evolução. O que foi mostrado aqui foram apenas alguns recursos que mais me chamam a atenção quando busco criar um código de fácil leitura/uso.
Recomendo bastante a leitura do guia de programação da linguagem e até do Framework Design Guidelines.
Até mais.