Esta é a questão da perspectiva interna do compilador.
Estou interessado em genéricos, não em modelos (C ++), por isso marquei a pergunta com C #. Não é Java, porque o AFAIK os genéricos nos dois idiomas diferem nas implementações.
Quando eu olho para idiomas sem genéricos, é bastante simples, você pode validar a definição de classe, adicioná-la à hierarquia e é isso.
Mas o que fazer com a classe genérica e, mais importante, como lidar com referências a ela? Como garantir que os campos estáticos sejam singulares por instanciações (ou seja, sempre que parâmetros genéricos forem resolvidos).
Digamos que eu recebo uma ligação:
var x = new Foo<Bar>();
Eu adiciono nova Foo_Bar
classe à hierarquia?
Atualização: Até agora encontrei apenas 2 posts relevantes, no entanto, mesmo eles não entram em muitos detalhes no sentido de "como fazer isso sozinho":
Respostas:
Cada instanciação genérica possui sua própria cópia da MethodTable (denominada de maneira confusa), que é onde os campos estáticos são armazenados.
Não sei se é útil pensar na hierarquia de classes como alguma estrutura que realmente existe no tempo de execução, é mais uma construção lógica.
Mas se você considerar MethodTables, cada um com um ponteiro indireto para sua classe base, para formar essa hierarquia, sim, isso adiciona nova classe à hierarquia.
fonte
Foo<string>
e eles não produziriam duas instâncias do campo estáticoFoo
.Eu vejo duas perguntas concretas reais lá. Provavelmente, você deseja fazer perguntas relacionadas adicionais (como uma pergunta separada com um link para este) para obter um entendimento completo.
Como os campos estáticos recebem instâncias separadas por instância genérica?
Bem, para membros estáticos que não estão relacionados aos parâmetros de tipo genérico, isso é bastante fácil (use um dicionário mapeado dos parâmetros genéricos para o valor).
Membros (estáticos ou não) relacionados aos parâmetros de tipo podem ser manipulados através do apagamento de tipo. Basta usar qualquer restrição mais forte (geralmente
System.Object
). Como as informações de tipo são apagadas após as verificações de tipo de compilador, isso significa que não serão necessárias verificações de tipo de tempo de execução (embora ainda possam existir conversões de interface no tempo de execução).Cada instância genérica aparece separadamente na hierarquia de tipos?
Não em genéricos .NET. Foi tomada a decisão de excluir a herança dos parâmetros de tipo, portanto, todas as instâncias de um genérico ocupam o mesmo local na hierarquia de tipos.
Provavelmente, essa foi uma boa decisão, porque a falha em procurar nomes de uma classe base seria incrivelmente surpreendente.
fonte
Foo<int>
eFoo<string>
atingiriam os mesmos dadosFoo
sem restrições.List<string>
eList<Form>
, comoList<T>
internamente possui um membro do tipoT[]
e não há restriçõesT
, o que você realmente obterá é um código de máquina que manipula umobject[]
. No entanto, como apenasT
instâncias são colocadas na matriz, tudo o que sai pode ser retornadoT
sem uma verificação de tipo adicional. Por outro lado, se você tivesseControlCollection<T> where T : Control
, a matriz internaT[]
se tornariaControl[]
.A maneira geral no front-end do compilador é ter dois tipos de instâncias de tipo, o tipo genérico (
List<T>
) e um tipo genérico vinculado (List<Foo>
). O tipo genérico define quais funções existem, quais campos e possui referências de tipo genérico onde quer queT
seja usado. O tipo genérico vinculado contém uma referência ao tipo genérico e um conjunto de argumentos de tipo. Que possui informações suficientes para você gerar um tipo concreto, substituindo as referências de tipo genéricas porFoo
ou quaisquer que sejam os argumentos de tipo. Esse tipo de distinção é importante quando você está fazendo inferência de tipo e precisa deduzirList<T>
versusList<Foo>
.Em vez de pensar em genéricos como modelos (que constroem várias implementações diretamente), pode ser útil pensar neles como construtores de tipos de linguagem funcional (onde os argumentos genéricos são como argumentos em uma função que fornece um tipo).
Quanto ao back-end, eu realmente não sei. Todo o meu trabalho com genéricos direcionou o CIL como back-end, para que eu pudesse compilá-los nos genéricos suportados lá.
fonte
List<T>
contém o tipo real (sua definição), enquantoList<Foo>
(obrigado pela parte da terminologia) com minha abordagem, mantém as declarações deList<T>
(é claro que agora vinculado aFoo
em vez deT
).