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).
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.
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
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
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.
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!