Estou com um pouco de dificuldade para entender como usaria covariância e contravariância no mundo real.
Até agora, os únicos exemplos que vi foram o mesmo exemplo antigo de matriz.
object[] objectArray = new string[] { "string 1", "string 2" };
Seria bom ver um exemplo que me permitisse usá-lo durante o meu desenvolvimento se eu pudesse vê-lo sendo usado em outro lugar.
c#
c#-4.0
covariance
Navalha
fonte
fonte
Respostas:
Digamos que você tenha uma Pessoa da classe e uma classe que dela deriva, Professor. Você tem algumas operações que aceitam um
IEnumerable<Person>
como argumento. Na sua turma da escola, você tem um método que retorna umIEnumerable<Teacher>
. A covariância permite que você use diretamente esse resultado para os métodos que usam umIEnumerable<Person>
, substituindo um tipo mais derivado por um tipo menos derivado (mais genérico). Contravariância, contra-intuitivamente, permite usar um tipo mais genérico, onde um tipo mais derivado é especificado.Consulte também Covariância e Contravariância em genéricos no MSDN .
Classes :
Uso :
fonte
Para completar…
fonte
void feed(IGobbler<Donkey> dg)
. Se você usasse um IGobbler <Quadruped> como parâmetro, não poderia passar um dragão que só come burros.Aqui está o que eu montei para me ajudar a entender a diferença
tldr
fonte
Contravariance
exemplo) quandoFruit
é o pai ou mãeApple
?As palavras-chave in e out controlam as regras de conversão do compilador para interfaces e delegados com parâmetros genéricos:
fonte
Aqui está um exemplo simples usando uma hierarquia de herança.
Dada a hierarquia de classes simples:
E no código:
Invariância (ou seja, parâmetros de tipo genérico * não * decorados com
in
ouout
palavras-chave)Aparentemente, um método como este
... deve aceitar uma coleção heterogênea: (o que faz)
No entanto, a transmissão de uma coleção de um tipo mais derivado falha!
Por quê? Como o parâmetro genérico
IList<LifeForm>
não é covariante -IList<T>
é invariável,IList<LifeForm>
apenas aceita coleções (que implementam IList) onde o tipo parametrizadoT
deve estarLifeForm
.Se a implementação do método
PrintLifeForms
era maliciosa (mas tem a mesma assinatura de método), a razão pela qual o compilador impede a passagemList<Giraffe>
se torna óbvia:Como
IList
permite adicionar ou remover elementos, qualquer subclasse deLifeForm
poderia, portanto, ser adicionada ao parâmetrolifeForms
e violaria o tipo de qualquer coleção de tipos derivados passada para o método. (Aqui, o método malicioso poderia tentar adicionar umZebra
avar myGiraffes
). Felizmente, o compilador nos protege deste perigo.Covariância (genérico com tipo parametrizado decorado com
out
)A covariância é amplamente usada com coleções imutáveis (ou seja, onde novos elementos não podem ser adicionados ou removidos de uma coleção)
A solução para o exemplo acima é garantir que um tipo de coleção genérica covariante seja usado, por exemplo
IEnumerable
(definido comoIEnumerable<out T>
).IEnumerable
não possui métodos para alterar a coleção e, como resultado daout
covariância, qualquer coleção com o subtipo deLifeForm
agora pode ser passada para o método:PrintLifeForms
agora pode ser chamado comZebras
,Giraffes
e qualquerIEnumerable<>
de qualquer subclasse deLifeForm
Contravariância (genérico com tipo parametrizado decorado com
in
)Contravariância é freqüentemente usada quando funções são passadas como parâmetros.
Aqui está um exemplo de uma função, que recebe um
Action<Zebra>
como parâmetro e a invoca em uma instância conhecida de uma Zebra:Como esperado, isso funciona muito bem:
Intuitivamente, isso falhará:
No entanto, isso consegue
e até isso também tem sucesso:
Por quê? Porque
Action
é definido comoAction<in T>
, isto écontravariant
, significa queAction<Zebra> myAction
, para , issomyAction
pode ser "no máximo" aAction<Zebra>
, masZebra
também são aceitáveis superclasses de menos derivadas de .Embora isso possa não ser intuitivo a princípio (por exemplo, como pode
Action<object>
ser passado como um parâmetro exigindoAction<Zebra>
?), Se você descompactar as etapas, notará que a função chamada (PerformZebraAction
) é responsável por transmitir dados (neste caso, umaZebra
instância ) para a função - os dados não provêm do código de chamada.Devido à abordagem invertida do uso de funções de ordem superior dessa maneira, no momento em que
Action
é invocada, é aZebra
instância mais derivada invocada contra azebraAction
função (passada como parâmetro), embora a própria função use um tipo menos derivado.fonte
in
palavra - chave é usada para a contravariância ?Action<in T>
eFunc<in T, out TResult>
são contrários ao tipo de entrada. (Meus exemplos usam os tipos existentes invariáveis (Lista), covariantes (IEnumerable) e contravariantes (Ação, Func))C#
isso não saberia disso.Basicamente, sempre que você tinha uma função que utiliza um Enumerable de um tipo, não era possível transmitir um Enumerable de um tipo derivado sem convertê-lo explicitamente.
Apenas para avisar sobre uma armadilha:
De qualquer forma, esse código é horrível, mas ele existe e a mudança de comportamento no C # 4 pode apresentar erros sutis e difíceis de encontrar se você usar uma construção como esta.
fonte
Do MSDN
fonte
Contravariância
No mundo real, você sempre pode usar um abrigo para animais em vez de um abrigo para coelhos, porque toda vez que um abrigo para animais hospeda um coelho, ele é um animal. No entanto, se você usar um abrigo de coelho em vez de um abrigo de animais, sua equipe poderá ser comida por um tigre.
No código, isso significa que se você tiver um
IShelter<Animal> animals
, você pode simplesmente escreverIShelter<Rabbit> rabbits = animals
, se você prometer e usoT
noIShelter<T>
apenas como parâmetros do método assim:e substituir um item com um mais genérico, ou seja, reduzir a variância ou introduzir contra variância.
Covariância
No mundo real, você sempre pode usar um fornecedor de coelhos em vez de um fornecedor de animais, porque toda vez que um fornecedor de coelhos lhe dá um coelho, ele é um animal. No entanto, se você usar um fornecedor de animais em vez de um fornecedor de coelho, poderá ser comido por um tigre.
No código, isso significa que, se você tiver um,
ISupply<Rabbit> rabbits
pode simplesmente escreverISupply<Animal> animals = rabbits
se prometer e usarT
oISupply<T>
método only only como retorno dos seguintes valores:e substituir um item com um mais derivada um, ou seja, aumentar a variância ou introduzir co variância.
Em suma, esta é apenas uma promessa verificável em tempo de compilação de você de que você trataria um tipo genérico de uma certa maneira para manter a segurança do tipo e não fazer com que ninguém comesse.
Você pode querer ler isso para entender melhor isso.
fonte
contravariance
é interessante. Estou lendo como indicação de um requisito operacional : que o tipo mais geral deve suportar os casos de uso de todos os tipos derivados. Portanto, nesse caso, o abrigo de animais deve ser capaz de oferecer suporte a todos os tipos de animais. Nesse caso, adicionar uma nova subclasse pode quebrar a superclasse! Ou seja, se adicionarmos um subtipo Tyrannosaurus Rex , ele poderá destruir nosso abrigo de animais existente .O delegado do conversor me ajuda a visualizar os dois conceitos trabalhando juntos:
TOutput
representa covariância em que um método retorna um tipo mais específico .TInput
representa contravariância em que um método é passado para um tipo menos específico .fonte