Eu sei que Java implementa polimorfismo paramétrico (genéricos) com apagamento. Eu entendo o que é apagamento.
Eu sei que o C # implementa polimorfismo paramétrico com reificação. Eu sei que isso pode fazer você escrever
public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}
ou que você pode saber em tempo de execução qual é o parâmetro type de algum tipo parametrizado, mas não entendo qual é .
- O que é um tipo reificado?
- O que é um valor reificado?
- O que acontece quando um tipo / valor é reificado?
c#
generics
reification
Martijn
fonte
fonte
if
icação é o processo de conversão de umaswitch
volta construção de umif
/else
, quando anteriormente tinha sido convertido a partir de umif
/else
a umswitch
...Respostas:
Reificação é o processo de pegar uma coisa abstrata e criar uma coisa concreta.
O termo reificação em C # genéricos refere-se ao processo pelo qual uma definição de tipo genérico e um ou mais argumentos de tipo genérico (a coisa abstrata) são combinados para criar um novo tipo genérico (a coisa concreta).
Para expressá-lo de forma diferente, é o processo de tirar a definição de
List<T>
eint
e produzir um concretoList<int>
tipo.Para entender melhor, compare as seguintes abordagens:
Nos genéricos Java, uma definição de tipo genérico é transformada em essencialmente um tipo genérico concreto compartilhado em todas as combinações de argumentos de tipo permitido. Portanto, vários tipos (nível de código-fonte) são mapeados para um tipo (nível binário) - mas, como resultado, as informações sobre os argumentos de tipo de uma instância são descartadas nessa instância (apagamento de tipo) .
Em genéricos C #, a definição de tipo genérico é mantida na memória em tempo de execução. Sempre que um novo tipo concreto é necessário, o ambiente de tempo de execução combina a definição de tipo genérico e os argumentos de tipo e cria o novo tipo (reificação). Portanto, obtemos um novo tipo para cada combinação dos argumentos de tipo, em tempo de execução .
System.Type
classe (mesmo que a combinação de argumentos de tipo genérico específica que você está instanciando não tenha feito ' (apareça diretamente no seu código-fonte).Nos modelos C ++, a definição do modelo é mantida na memória em tempo de compilação. Sempre que uma nova instanciação de um tipo de modelo é necessária no código-fonte, o compilador combina a definição do modelo e os argumentos do modelo e cria o novo tipo. Portanto, obtemos um tipo exclusivo para cada combinação dos argumentos do modelo, em tempo de compilação .
fonte
Reificação significa geralmente (fora da ciência da computação) "tornar algo real".
Na programação, algo é reificado se pudermos acessar informações sobre ele na própria linguagem.
Para dois exemplos completamente não genéricos de algo que o C # faz e não tem reificado, vamos usar métodos e acesso à memória.
As linguagens OO geralmente têm métodos (e muitas que não têm funções semelhantes, embora não estejam ligadas a uma classe). Como tal, você pode definir um método em um idioma assim, chamá-lo, talvez substituí-lo e assim por diante. Nem todas essas linguagens permitem que você lide com o próprio método como dados para um programa. C # (e, na verdade, .NET em vez de C #) permite fazer uso de
MethodInfo
objetos que representam os métodos; portanto, em métodos de C # são reificados. Métodos em C # são "objetos de primeira classe".Todas as linguagens práticas têm alguns meios para acessar a memória de um computador. Em uma linguagem de baixo nível como C, podemos lidar diretamente com o mapeamento entre os endereços numéricos usados pelo computador, de modo que gostos
int* ptr = (int*) 0xA000000; *ptr = 42;
sejam razoáveis (desde que tenhamos um bom motivo para suspeitar que o acesso ao endereço de memória0xA000000
dessa maneira tenha vencido ' explodir algo). No C #, isso não é razoável (podemos forçá-lo no .NET, mas com o gerenciamento de memória do .NET movendo as coisas, é pouco provável que seja útil). C # não possui endereços de memória reificados.Portanto, como refied significa "tornado real" um "tipo reificado" é um tipo sobre o qual podemos "falar" no idioma em questão.
Em genéricos, isso significa duas coisas.
Uma delas é que
List<string>
é um tipo tãostring
ouint
são. Podemos comparar esse tipo, obter seu nome e perguntar sobre ele:Uma conseqüência disso é que podemos "falar sobre" os tipos de parâmetros de um método genérico (ou método de uma classe genérica) dentro do próprio método:
Como regra, fazer isso demais é "fedido", mas tem muitos casos úteis. Por exemplo, veja:
Isso não faz muitas comparações entre o tipo de
TSource
e vários tipos para comportamentos diferentes (geralmente um sinal de que você não deveria ter usado genéricos), mas se divide entre um caminho de código para tipos que podem sernull
(deve retornarnull
se nenhum elemento encontrado e não deve fazer comparações para encontrar o mínimo se um dos elementos comparados fornull
) e o caminho do código para tipos que não podem sernull
(devem ser lançados se nenhum elemento for encontrado e não precisam se preocupar com a possibilidade denull
elementos )Como
TSource
é "real" dentro do método, essa comparação pode ser feita em tempo de execução ou em tempo de execução (geralmente tempo de execução, certamente o caso acima faria isso no momento da execução e não produziria código de máquina para o caminho não percorrido) e temos versão "real" separada do método para cada caso. (Embora como uma otimização, o código da máquina seja compartilhado para diferentes métodos para diferentes parâmetros do tipo de referência, porque pode ser sem afetar isso e, portanto, podemos reduzir a quantidade de código da máquina emitida).(Não é comum falar sobre reificação de tipos genéricos em C #, a menos que você também lide com Java, porque em C # nós consideramos essa reificação como garantida; todos os tipos são reificados. Em Java, tipos não genéricos são referidos como reificados, porque é uma distinção entre eles e tipos genéricos).
fonte
Min
faz acima útil? Caso contrário, é muito difícil cumprir seu comportamento documentado.Enumerable.Min<TSource>
é diferente, pois ele não lança para tipos não-referência em uma coleção vazia, mas retorna o padrão (TSource) e está documentado apenas como "Retorna o valor mínimo em uma sequência genérica". Eu diria que ambos devem lançar uma coleção vazia ou que um elemento "zero" deve ser passado como linha de base e o comparador / função de comparação sempre deve ser passada))Como o duffymo já observou , "reificação" não é a principal diferença.
Em Java, os genéricos estão basicamente lá para melhorar o suporte ao tempo de compilação - ele permite que você use coleções fortemente tipadas, por exemplo, em seu código, e tenha a segurança de tipo manipulada para você. No entanto, isso só existe no momento da compilação - o código de código compilado não tem mais noção de genéricos; todos os tipos genéricos são transformados em tipos "concretos" (usando
object
se o tipo genérico é ilimitado), adicionando conversões de tipo e verificações de tipo, conforme necessário.No .NET, os genéricos são um recurso integrante do CLR. Quando você compila um tipo genérico, ele permanece genérico na IL gerada. Não é apenas transformado em código não genérico como em Java.
Isso tem vários impactos sobre como os genéricos funcionam na prática. Por exemplo:
SomeType<?>
permitir que você passe qualquer implementação concreta de um determinado tipo genérico. O C # não pode fazer isso - todo tipo genérico ( reificado ) específico é de seu próprio tipo.object
. Isso pode ter um impacto no desempenho ao usar tipos de valor em tais genéricos. Em C #, quando você usa um tipo de valor em um tipo genérico, ele permanece um tipo de valor.Para dar uma amostra, vamos supor que você tenha um
List
tipo genérico com um argumento genérico. Em Java,List<String>
eList<Int>
acabará sendo exatamente o mesmo tipo em tempo de execução - os tipos genéricos realmente existem apenas para código em tempo de compilação. Todas as chamadas para, por exemploGetValue
, serão transformadas em(String)GetValue
e(Int)GetValue
respectivamente.Em C #,
List<string>
eList<int>
existem dois tipos diferentes. Eles não são intercambiáveis e sua segurança de tipo também é aplicada em tempo de execução. Não importa o que você faz,new List<int>().Add("SomeString")
vai não trabalho - o armazenamento subjacente emList<int>
é realmente alguns array de inteiros, enquanto em Java, é necessariamente umaobject
matriz. Em C #, não há elencos envolvidos, nem boxe etc.Isso também deve deixar óbvio por que o C # não pode fazer a mesma coisa que o Java
SomeType<?>
. Em Java, todos os tipos genéricos "derivados de"SomeType<?>
acabam sendo exatamente o mesmo tipo. No C #, todos os váriosSomeType<T>
s específicos são de seu próprio tipo separado. Removendo verificações em tempo de compilação, é possível passar emSomeType<Int>
vez deSomeType<String>
(e realmente, tudo o que issoSomeType<?>
significa é "ignorar verificações em tempo de compilação para o tipo genérico especificado"). Em C #, não é possível, nem mesmo para tipos derivados (ou seja, você não pode fazerList<object> list = (List<object>)new List<string>();
mesmo questring
seja derivadoobject
).Ambas as implementações têm seus prós e contras. Houve algumas vezes em que eu adoraria ser capaz de permitir
SomeType<?>
como argumento em C # - mas simplesmente não faz sentido o modo como os genéricos de C # funcionam.fonte
List<>
,Dictionary<,>
e assim por diante, em C #, mas a diferença entre isso e uma determinada lista ou dicionário concreto requer um pouco de reflexão para colmatar. A variação nas interfaces ajuda em alguns dos casos em que poderíamos querer preencher essa lacuna facilmente, mas não todos.List<>
para instanciar um novo tipo genérico específico - mas ainda significa criar o tipo específico que você deseja. Mas você não pode usarList<>
como argumento, por exemplo. Mas sim, pelo menos isso permite que você preencha a lacuna usando a reflexão.T
pode satisfazer uma restrição do tipo local de armazenamentoU
são quandoT
eU
são do mesmo tipo, ouU
é um tipo que pode conter uma referência a uma instância deT
. Não seria possível ter significativamente um local de armazenamento do tipo,SomeType<?>
mas em teoria seria possível ter uma restrição genérica desse tipo.Reificação é um conceito de modelagem orientada a objetos.
Reify é um verbo que significa "tornar algo abstrato real" .
Quando você faz programação orientada a objetos, é comum modelar objetos do mundo real como componentes de software (por exemplo, janela, botão, pessoa, banco, veículo etc.)
Também é comum reificar conceitos abstratos em componentes também (por exemplo, WindowListener, Broker, etc.)
fonte