Skip to content

Instantly share code, notes, and snippets.

@kroltan

kroltan/blog.md Secret

Created September 27, 2017 00:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kroltan/b6263cc6a6a3bb54867bc45f28fee941 to your computer and use it in GitHub Desktop.
Save kroltan/b6263cc6a6a3bb54867bc45f28fee941 to your computer and use it in GitHub Desktop.

Auto-tipos - Genéricos Repetitivos em C#

Introdução

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.

https://gist.github.com/107e6b3db294d3b5e73e1f57aa4820a0

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:

https://gist.github.com/b34e9f899f56e80fbd56c86845640968

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":

https://gist.github.com/df84b20379d50664800eae700ebfdf87

Esta classe pode ser utilizada da seguinte maneira:

https://gist.github.com/2d0184aa084bcc396cb8e27e2c22ff3d

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

https://gist.github.com/c59072173e4dff964b261dac0f2e66f9

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?

https://gist.github.com/179af80cbbf02684651615669b1d789c

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:

https://gist.github.com/459f2428bbcf1bc122c1633a1c861eec

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:

https://gist.github.com/b52174078dbd99f2e36b43466f9598d3

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:

https://gist.github.com/f59080f09e4e4bcdba01924230763a0d

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:

https://gist.github.com/a9a43cbb8b830d5c830d8ec7cc1917b8

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 ao seu nome como apenas uma "curiosidade" na maior parte dos casos. Mas se você conhece algum outro uso deste padrão, compartilhe-o!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment