Covariância e Contravariância no C#

Introdução a conceitos de programação orientada a objetos com a linguagem C#.

Leon Maia ⌘
The Journal by Leon Maia

--

No C# 4.0, podemos adicionar anotações in ou out nos parâmetros de tipo genéricos para especificar quando eles devem se comportar como covariância ou contravariância. Isso é principalmente útil quando já está definido em uma interface. Para você entender melhor, covariância significa que você pode usar IEnumerable<string> onde IEnumerable<object> é esperado. Contravariância permite que você passe um IComparable<object> como um argumento para um método que pede um IComparable.

Covariância

Vamos começar vendo o que covariância significa. Assumindo que todo Animal possui um nome, nós podemos escrever a função seguinte que imprime o nome de todos os animais de uma determinada coleção:

void PrintAnimalNames(IEnumerable<Animal> animals) {
for(var animal in animals)
Console.WriteLine(animal.Name);
}

Se criássemos uma coleção de cachorros, poderíamos usar o PrintAnimals para imprimir seus nomes?

IEnumerable dogs = new List { new ("Wolverine") };
PrintAnimals(dogs);

Você vai ver que esse código compila, e funciona perfeitamente. O compilador usa covariância para converter IEnumerable<Dog> para IEnumerable<Animal>. Isso só funciona porque a interface de IEnumerable é marcada como covariante usando a anotação out. Usar uma coleção de cachorros como argumento não está errado, pois cachorros são animais.

Contravariância

Contravariância funciona do modo oposto de covariância. Vamos dizer que temos um método que cria alguns cachorros e os compara usando um IComparer<Dog>.

void CompareDog(IComparer comparer) {
var dog1 = new Dog("Logan");
var dog2 = new Dog("Wolverine");

if (comparer.Compare(dog2, dog1) > 0)
Console.WriteLine("Wolverine ganhou!");
}

O objeto comparer aceita Cachorros como argumentos, mas nunca irá retornar um cachorro como resultado. Agora, graças à contravariância, nós podemos criar um comparer que pode comparar animais e usar ele como um argumento para CompareDogs:

IComparator compareAnimals = new AnimalSizeComparator();
CompareDogs(compareAnimals);

O compilador aceita esse código porque a interface IComparer é contravariante e o seu parâmetro de tipo genérico é anotado com in. O objeto compareAnimals que nós criamos sabe como comparar animais, logo, ele sabe como fazer a comparação entre dois cachorros.

--

--

Software Engineer at Zendesk. In the past worked helping to build the architecture of Globo’s video streaming platform. Visit my blog: https://leonmaia.me