Auto-tipos: Genéricos Repetitivos em C#

O sistema de tipos da linguagem C#, apesar de ter algumas limitações, é surpreendentemente poderoso. Em particular, é possível definir tipos genéricos de maneira auto-referente, que abre algumas possibilidades interessantes.

Em particular, é possível implementar um padrão conhecido como “Curiously Repeating Template Pattern” (“Padrão de modelo curiosamente repetitivo”, daqui adiante chamado de CRTP).

Analizando o CRTP

O CRTP é simples de ser expressado, mas seu funcionamento pode não ser óbvio. Consiste basicamente de uma “herança invertida”. Há duas partes que devem ser observadas. A base, e a implementação.

A base define um tipo (classe, estrutura ou interface) o qual recebe um parâmetro de tipo com uma cláusula limitando apenas ao uso de derivados deste tipo.

Daí vem o nome “curiosamente repetitivo”. Qualquer tipo que herde de Base terá que passar algum T que satisfaça o requisito. Este T é o descendente em si:

O que isto significa? Com este padrão, Base pode conter uma implementação que utilize Derivado diretamente, aumentando a exatidão dos tipos de uma API, além de permitir o uso de membros estáticos com acesso ao descendente.

Uma grande vantagem deste padrão é o fato de trazer o polimorfismo do tempo de execução para o tempo de compilação. Ou seja, são necessários menos casts em situações onde o contrato da interface seria óbvio perante documentação, mas inexpressível.

Esta restrição garante que o T referido seja sempre um tipo que suporte operações de Base<T>, essencialmente garantindo acesso completo à toda a funcionalidade de Base, incluindo membros estáticos, sem conversões explícitas.

Exemplo

Uma possível aplicação deste padrão é para a criação de instâncias genericamente sem acesso a uma instância do criador. Neste caso, implementaremos um tipo “Singleton”:

Esta classe pode ser utilizada da seguinte maneira:

Assim, é possível escrever um código limpo sem consultas à documentação para casts ou similares:

Auto-tipos

Considere a palavra-chave this. Ela é definida implicitamente para qualquer instância e contém o valor da instância em si. Esta é uma "auto-variável", pois o objeto refere-se a si mesmo.

Agora tente imaginar o equivalente do this, porém, ao invés de representar o valor, represente o tipo da classe onde está contido. Vamos chamar esse tipo de This (algumas linguagens preferem Self).

Quais são as implicações de um tipo assim? Por que não simplesmente escrever o tipo manualmente?

Neste caso, é óbvio que o retorno de Colher tem o mesmo tipo da classe na qual o método foi declarado. Mas em casos mais complexos, fica um pouco menos conveniente:

Observe que a assinatura e corpo do método Preparar ficaram muito mais convolutos. Vamos aplicar uma refatoração, trocando quaisquer referências ao próprio tipo para o hipotético This:

Emulação Através de CRTP

Muito mais conciso, não? Mas é tudo teórico, não é possível fazer isso no C# atual!

Bom, talvez seja possível! O leitor atento perceberá que esta situação é muito similar ao exemplo de aplicação do CRTP. E é mesmo, pois é possível usar o CRTP para emular auto-tipos em C#! Vamos reescrever nossa Sopa em algo compilável:

Essa implementação é um pouco menos ergonômica que o This hipotético, mas traz um possível benefício de anotar como parte do contrato que a classe precisa utilizar a si mesma concretamente:

Porém, esta emulação não é perfeita. Como você pode ter visto, o descendente que tem o compromisso “moral” de passar a si mesmo como parâmetro para o This. Na prática, o sistema ainda é subversível caso o descendente passe outro tipo que satisfaça as restrições como parâmetro. Como auto-tipo, esta implementação é uma boa aproximação, mas apenas uma aproximação.

Conclusão

Conhecemos o CRTP, uma técnica razoavelmente popular na terra do C++, e como pode ser implementado em C#. O principal e mais óbvio uso é na simulação de auto-tipos.

Entretanto, logo vimos que o sistema de tipos do C# ainda é relativamente limitado quando comparado com linguagens que utilizam genéricos pervasivamente, e vimos as limitações do método apresentado.

Em minha experiência, o CRTP tem poucas aplicações em C#, fazendo jus a parte de seu nome — apenas uma “curiosidade” na maior parte dos casos. Mas se você conhece algum outro uso deste padrão, compartilhe-o!


Originally published at gist.github.com.