Encontrei isso em um livro que estou lendo em C #, mas estou tendo dificuldade em entendê-los, provavelmente devido à falta de contexto.
Existe uma boa explicação concisa do que eles são e para que são úteis lá fora?
Editar para esclarecimento:
Interface covariante:
interface IBibble<out T>
.
.
Interface contravariante:
interface IBibble<in T>
.
.
c#
.net
interface
covariance
contravariance
NibblyPig
fonte
fonte
Respostas:
Com
<out T>
, você pode tratar a referência de interface como uma para cima na hierarquia.Com
<in T>
, você pode tratar a referência da interface como uma para baixo na hierarquia.Deixe-me tentar explicar em termos mais ingleses.
Digamos que você esteja recuperando uma lista de animais de seu zoológico e pretenda processá-los. Todos os animais (em seu zoológico) têm um nome e uma identificação exclusiva. Alguns animais são mamíferos, alguns são répteis, alguns são anfíbios, alguns são peixes, etc., mas são todos animais.
Então, com sua lista de animais (que contém animais de diferentes tipos), você pode dizer que todos os animais têm um nome, então obviamente seria seguro obter o nome de todos os animais.
No entanto, e se você tiver apenas uma lista de peixes, mas precisar tratá-los como animais, isso funciona? Intuitivamente, deve funcionar, mas no C # 3.0 e anteriores, este trecho de código não compilará:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
A razão para isso é que o compilador não "sabe" o que você pretende ou pode fazer com a coleção de animais depois de recuperá-la. Pelo que se sabe, pode haver uma maneira
IEnumerable<T>
de colocar um objeto de volta na lista, e isso potencialmente permitiria que você coloque um animal que não seja um peixe em uma coleção que supostamente contém apenas peixes.Em outras palavras, o compilador não pode garantir que isso não seja permitido:
animals.Add(new Mammal("Zebra"));
Portanto, o compilador simplesmente se recusa a compilar seu código. Isso é covariância.
Vejamos a contravariância.
Como nosso zoológico pode lidar com todos os animais, certamente pode lidar com peixes, então vamos tentar adicionar alguns peixes ao nosso zoológico.
No C # 3.0 e anteriores, isso não compila:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal> fishes.Add(new Fish("Guppy"));
Aqui, o compilador pode permitir esse trecho de código, embora o método retorne
List<Animal>
simplesmente porque todos os peixes são animais, então, se apenas alterarmos os tipos para este:List<Animal> fishes = GetAccessToFishes(); fishes.Add(new Fish("Guppy"));
Então funcionaria, mas o compilador não pode determinar que você não está tentando fazer isso:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal> Fish firstFist = fishes[0];
Como a lista é na verdade uma lista de animais, isso não é permitido.
Portanto, contra e covariância é como você trata as referências de objetos e o que você pode fazer com elas.
As palavras
in
-out
chave e em C # 4.0 marcam especificamente a interface como uma ou outra. Comin
, você pode colocar o tipo genérico (geralmente T) em posições de entrada , o que significa argumentos de método e propriedades somente de gravação.Com
out
, você pode colocar o tipo genérico em posições de saída , que são valores de retorno de método, propriedades somente leitura e parâmetros de método de saída.Isso permitirá que você faça o que pretendia fazer com o código:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish> // since we can only get animals *out* of the collection, every fish is an animal // so this is safe
List<T>
tem direções de entrada e saída em T, portanto, não é nem covariante nem contra-variante, mas uma interface que permite adicionar objetos, como este:interface IWriteOnlyList<in T> { void Add(T value); }
permitiria que você fizesse isso:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns IWriteOnlyList<Animal> fishes.Add(new Fish("Guppy")); <-- this is now safe
Aqui estão alguns vídeos que mostram os conceitos:
Aqui está um exemplo:
namespace SO2719954 { class Base { } class Descendant : Base { } interface IBibbleOut<out T> { } interface IBibbleIn<in T> { } class Program { static void Main(string[] args) { // We can do this since every Descendant is also a Base // and there is no chance we can put Base objects into // the returned object, since T is "out" // We can not, however, put Base objects into b, since all // Base objects might not be Descendant. IBibbleOut<Base> b = GetOutDescendant(); // We can do this since every Descendant is also a Base // and we can now put Descendant objects into Base // We can not, however, retrieve Descendant objects out // of d, since all Base objects might not be Descendant IBibbleIn<Descendant> d = GetInBase(); } static IBibbleOut<Descendant> GetOutDescendant() { return null; } static IBibbleIn<Base> GetInBase() { return null; } } }
Sem essas marcas, o seguinte poderia compilar:
public List<Descendant> GetDescendants() ... List<Base> bases = GetDescendants(); bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
ou isto:
public List<Base> GetBases() ... List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases as Descendants
fonte
Este post é o melhor que li sobre o assunto
Em suma, covariância / contravariância / invariância lida com a conversão automática de tipos (da base para a derivada e vice-versa). Esses tipos de conversão são possíveis apenas se algumas garantias forem respeitadas em termos de ações de leitura / gravação realizadas nos objetos moldados. Leia a postagem para mais detalhes.
fonte