Como a variação do site de uso do Java se compara à variação do site de declaração do C#?

StackOverflow https://stackoverflow.com/questions/4231305

  •  26-09-2019
  •  | 
  •  

Pergunta

Meu entendimento é que a especificação da variação para genéricos em C# acontece no nível da declaração de tipo:ao criar seu tipo genérico, você especifica a variação dos argumentos de tipo.Em Java, por outro lado, a variância é especificada onde um genérico é usado:ao criar uma variável de algum tipo genérico, você especifica como seus argumentos de tipo podem variar.

Quais são os prós e os contras de cada opção?

Foi útil?

Solução

Vou apenas responder às diferenças entre a variação do site de declaração e do site de uso, pois, embora os genéricos C# e Java difiram de muitas outras maneiras, essas diferenças são principalmente ortogonais à variação.

Em primeiro lugar, se bem me lembro, a variação do site de uso é estritamente mais poderosa que a variação do site de declaração (embora ao custo da concisão), ou pelo menos os curingas do Java são (que na verdade são mais poderosos que a variação do site de uso).Esse poder aumentado é particularmente útil para linguagens nas quais construções com estado são muito usadas, como C# e Java (mas Scala muito menos, especialmente porque suas listas padrão são imutáveis).Considerar List<E> (ou IList<E>).Como possui métodos para adicionar E's e obter E's, é invariante em relação a E e, portanto, a variação do local de declaração não pode ser usada.No entanto, com a variação do local de uso, você pode apenas dizer List<+Number> para obter o subconjunto covariante de List e List<-Number> para obter o subconjunto contravariante de List.Em uma linguagem de site de declaração, o designer da biblioteca teria que criar interfaces separadas (ou classes, se você permitir herança múltipla de classes) para cada subconjunto e ter List estender essas interfaces.Se o designer da biblioteca não fizer isso (observe que o C# IEnumerable apenas um pequeno subconjunto da porção covariante de IList), então você está sem sorte e terá que recorrer aos mesmos aborrecimentos que teria em um idioma sem qualquer tipo de variação.

Essas são as vantagens da herança do site de uso sobre a herança do site de declaração.A vantagem da herança do site de declaração sobre a herança do site de uso é basicamente concisa para o usuário (desde que o designer se esforce para separar cada classe/interface em suas porções covariantes e contravariantes).Para algo como IEnumerable ou Iterator, é bom não precisar especificar a covariância toda vez que você usa a interface.Java tornou isso especialmente irritante ao usar uma sintaxe longa (exceto para bivariância para a qual a solução Java é basicamente ideal).

É claro que estas duas características linguísticas podem coexistir.Para parâmetros de tipo que são naturalmente covariantes ou contravariantes (como em IEnumerable/Iterator), declare isso na declaração.Para parâmetros de tipo que são naturalmente invariantes (como em (I)List), declare que tipo de variação você deseja cada vez que usá-lo.Apenas não especifique uma variação de site de uso para argumentos com uma variação de site de declaração, pois isso apenas torna as coisas confusas.

Há outras questões mais detalhadas que não abordei (como como os curingas são realmente mais poderosos do que a variação do site de uso), mas espero que isso responda à sua pergunta sobre o seu conteúdo.Admito que sou inclinado à variação do site de uso, mas tentei retratar as principais vantagens de ambos que surgiram em minhas discussões com programadores e pesquisadores de linguagem.

Outras dicas

A maioria das pessoas parece preferir a variação do local da declaração, porque torna mais fácil para Usuários da biblioteca (ao mesmo tempo que torna isso um pouco mais difícil para o desenvolvedor da biblioteca, embora eu argumente que o desenvolvedor da biblioteca precisa pensar na variação, independentemente de onde a variação é realmente escrita).

Mas tenha em mente que nem Java nem C# são exemplos de bom design de linguagem.

Enquanto Java acertou a variação e funcionou independentemente da JVM devido a melhorias de VM compatíveis no Java 5 e apagamento de tipo, a variação do site de uso torna o uso um pouco complicado e a implementação específica do apagamento de tipo atraiu críticas bem merecidas.

C#O modelo de variação do site de declaração tira o fardo do usuário da biblioteca, mas durante a introdução de genéricos reificados, eles basicamente construíram as regras de variação em sua VM.Mesmo hoje eles não podem apoiar totalmente a co-/contravariância por causa desse erro (e a introdução não compatível com versões anteriores das classes de coleção reificadas dividiu os programadores em dois campos).

Isto representa uma restrição difícil para todas as linguagens direcionadas ao CLR e é uma das razões pelas quais as linguagens de programação alternativas são muito mais ativas na JVM, embora pareça que o CLR tenha "recursos muito melhores".

Vamos olhar para escala:Scala é um híbrido funcional totalmente orientado a objetos rodando na JVM.Eles usam apagamento de tipo como Java, mas tanto a implementação de Genéricos quanto a variação (site de declaração) são mais fáceis de entender, mais diretas e poderosas que Java (ou C#), porque a VM não impõe regras sobre como a variação deve ser trabalhar.O compilador Scala verifica as notações de variação e pode rejeitar código-fonte incorreto em tempo de compilação em vez de lançar exceções em tempo de execução, enquanto os arquivos .class resultantes podem ser usados ​​perfeitamente em Java.

Uma desvantagem da variação do site de declaração é que ela parece dificultar a inferência de tipos em alguns casos.

Ao mesmo tempo, Scala pode usar tipos primitivos sem encaixá-los em coleções como em C# usando o @specialized anotação que diz ao compilador Scala para gerar uma ou múltiplas implementações adicionais de uma classe ou método especializado para o tipo primitivo solicitado.

Scala também pode "quase" reificar genéricos usando Manifestos que lhes permite recuperar os tipos genéricos em tempo de execução como em C#.

Desvantagens dos genéricos do estilo Java

Uma conseqüência é que a versão Java funciona apenas com tipos de referência (ou tipos de valor em caixa) e não em tipos de valor. IMO, essa é a maior desvantagem, pois impede genéricos de alto desempenho em muitos cenários e requer redação manual de tipos especializados.

Ele não garante um invariante como "Esta lista contém apenas objetos do tipo X" e precisa de verificações de tempo de execução em todos os getters. O tipo genérico realmente existe.

Ao usar a reflexão, você não pode pedir uma instância de um objeto genérico quais parâmetros genéricos ele possui.

Vantagens dos genéricos do estilo Java

Você obtém variação/pode ser lançado entre diferentes parâmetros genéricos.

Java: genéricos da variação do site de uso desde Java 5. Matrizes covariantes quebradas com uma sintaxe diferente desde 1.0. Nenhum tipo de tempo de execução Informações de genéricos.

C#: Genéricos da variação do site de uso desde C# 2.0. Adicionada variação no local da declaração em C# 4.0. Matrizes covariantes quebradas com uma sintaxe diferente desde 1,0 (edição idêntica ao Java). "Reiificado" genéricos, o que significa que as informações do tipo não estão perdidas no momento da compilação.

Scala: variação do site de uso/declaração desde versões iniciais do idioma (pelo menos desde 2008). As matrizes não são um recurso de idioma separado; portanto, você usa as mesmas regras de sintaxe e digite genéricas. Certas coleções são implementadas no nível da VM com as matrizes JVM, para que você obtenha desempenho de tempo de execução igual ou melhor em comparação com o código Java.

Para elaborar o problema de segurança do tipo C#/java: você pode lançar um cachorro [] para um animal de estimação [] e adicionar um gato e acionar um erro de tempo de execução que não é capturado no momento da compilação. O Scala implementou isso corretamente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top