A expressividade do C#

Faz 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.