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();
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();
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();
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. Com in
, 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();
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();
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)
{
IBibbleOut<Base> b = GetOutDescendant();
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