Como a covariância e contra-variância genéricas são implementadas no C # 4.0?

106

Não participei do PDC 2008, mas ouvi algumas notícias de que o C # 4.0 foi anunciado para oferecer suporte à covariância e contra-variância genérica. Ou seja, List<string>pode ser atribuído a List<object>. Como poderia ser?

No livro C # in Depth de Jon Skeet , é explicado por que os genéricos C # não suportam covariância e contra-variância. É principalmente para escrever código seguro. Agora, o C # 4.0 mudou para suportá-los. Isso traria o caos?

Alguém conhece os detalhes sobre C # 4.0 pode dar alguma explicação?

Morgan Cheng
fonte
Aqui está um bom artigo que cobre as próximas implementações de covariância e contra-variância em delegados e interfaces em C # 4.0: LINQ Farm: Covariância e Contravariância em C # 4.0
CMS
Anders Noråse explica em C # 4.0 - Covariância e contra-variância o conceito e mostra que já é suportado hoje em IL desde .NET 2.0.
Thomas Freudenberg

Respostas:

155

A variação será suportada apenas de forma segura - na verdade, usando as habilidades que o CLR já possui. Portanto, os exemplos que dou no livro de tentar usar um List<Banana>como List<Fruit>(ou seja o que for) ainda não funcionarão - mas alguns outros cenários funcionarão.

Em primeiro lugar, ele só terá suporte para interfaces e delegados.

Em segundo lugar, requer que o autor da interface / delegado decore os parâmetros de tipo como in(para contravariância) ou out(para covariância). O exemplo mais óbvio é IEnumerable<T>aquele que só permite que você retire valores "fora" dele - não permite que você adicione novos. Isso vai se tornar IEnumerable<out T>. Isso não prejudica a segurança de tipo, mas permite que você retorne um IEnumerable<string>de um método declarado para retornar, IEnumerable<object>por exemplo.

A contravariância é mais difícil de dar exemplos concretos de uso de interfaces, mas é fácil com um delegado. Considere Action<T>- isso representa apenas um método que recebe um Tparâmetro. Seria bom ser capaz de converter perfeitamente e usar um Action<object>como um Action<string>- qualquer método que receba um objectparâmetro funcionará bem quando for apresentado com um string. Obviamente, C # 2 já tem covariância e contravariância de delegados até certo ponto, mas por meio de uma conversão real de um tipo de delegado para outro (criando uma nova instância) - consulte P141-144 para exemplos. C # 4 tornará isso mais genérico e (acredito) evitará a criação de uma nova instância para a conversão. (Em vez disso, será uma conversão de referência.)

Espero que isso esclareça um pouco - por favor, me avise se não fizer sentido!

Jon Skeet
fonte
3
Portanto, isso significa que se a classe for declarada como "List <out T>", ela NÃO deve ter uma função de membro como "void Add (T obj)"? O compilador C # 4.0 reportará um erro nisso, certo?
Morgan Cheng
1
Morgan: Esse é certamente o meu entendimento, sim.
Jon Skeet
4
mais uma vez, uma de suas respostas aqui no SO me ajudou imediatamente a melhorar alguns códigos. Obrigado!
Marcar
@ Ark-kun: Sim, estou ciente disso. Portanto, o "ainda não funciona" na mesma frase. (E também estou ciente das razões.)
Jon Skeet
@JonSkeet É correto que "você só pode usar um List<Banana>como IList<Fruit>", como disse @Ark-kun? Se sim, como isso é possível, embora o parâmetro de tipo da IList<T>interface não seja definido como covariante (não out T, mas simplesmente T).
gehho