Covariância e Contravariância em C#, Parte Cinco: Contravariância Dupla

4/25/2008 12:14:00 PM By Felipe Pessoto

Na parte quatro da série, falamos sobre como poderiamos ter um tipo de delegate que aceitaria ter valores covariantes no seu tipo de retorno e contravariantes no tipo recebido por argumento. Por exemplo, nós podemos ter um delegate action contravariante:

delegate void Action< -A > (A a);

e então temos

Action<Animal> action1 = (Animal a)=>{ Console.WriteLine(a.NomeLatin); };
Action<Girafa> action2 = action1;

Porque o invocador do action2 irá sempre passar algo que o action1 possa manipular.

Baseado no que vimos até agora no que diz respeito à variância concluimos que "o que está entrando deve ser contravariante, o que sai deve ser variante". Embora pareça que este seria um uso comum de variância que deveria ser implementada numa futura versão do C#, a realidade é um pouco mais complicada. Há uma situação onde é válido usar um argumento covariante como parâmetro de um delegate.

Suponha que você queira criar uma programação funcional de "ordem maior". Por exemplo, talvez você queira definir uma meta-ação – um delegate que recebe actions e faz alguma coisa com eles:

delegate void Meta<A>(Action<A> action);

Por exemplo:

Meta<Mamifero> meta1 = (Action<Mamifero> action)=>{action(new Girafa());};

// A próxima linha é válida porque Action<Animal> é menor que Action<Mamifero>;
// lembrando que Action é contravariante
meta1(action1); 

Então este Meta recebe um Action de Mamiferos, ou o action1 acima, o qual exibe o nome em Latin de qualquer Animal, o que quer dizer que pode ser usado para os Mamifero – e então invoca aquela action usando o new Girafa.

É evidente que o parâmetro de tipo A é usado em uma posição de entrada na definição de Meta<A>, então nós devemos ser capazes de usar contravariancia, certo? Suponha que sim. Isso significa que essa atribuição seria válida:

Meta<Tigre> meta2 = meta1; // deve ser válida se Meta é contraviante no parâmetro

Mas isso significa que isso seria válido:

Action<Tigre> action3 = Tigre=>{ Tigre.Rosnar(); };
meta2(action3);

Seguindo a lógica você verá que no final acabamos chamando (new Girafa()).Rosnar(), o qual claramente viola ambas as regras: do sistema e da natureza. Pode ser um pouco complicado até entender toda a lógica, mas escrevendo o código pode ajudar:

static void Main(string[] args)
        {
            Action<int> a;
 
            Action<Animal> action1 = metodo;
            Action<Girafa> action2 = action1;
 
            Meta<Mamifero> meta1 = metodo2;
 
            meta1(action1);
 
 
            Meta<Tigre> meta2 = meta1;
            Action<Tigre> action3 = metodo3;
            meta2(action3);
 
            Meta<Animal> meta3 = meta1;
 
 
        }
 
        static void metodo3(Tigre Tigre)
        {
            Tigre.Rosnar();
        }
 
        static void metodo2(Action<Mamifero> action)
        {
            action(new Girafa());
        }
 
        static void metodo(Animal a)
        {
            Console.WriteLine(a.NomeLatin);
        }
 
        public delegate void Meta<A>(Action<A> action);

No final do Main, você pode ver que o meta2 é igual ao meta1. O meta1 chama o método 2. O método 2 vai chamar o action passado(no caso o action3 que chama o metodo3) passando um new Girafa() como parâmetro. Neste momento que acontece a inconsistência. Pois estamos chamando o método3 passando uma Girafa, e o método3 iria chamar o método new Girafa()).Rosnar().

Então Meta<A> não pode ser contravariante em A. No entanto ele pode ser covariante:

Meta<Animal> meta3 = meta1; // válido se Meta for covariante

Agora tudo funciona. meta3 recebe um Action sobre Animals e então passa um Girafa para a Action.

Contravariância é complicado. O fato de se inverter o maior/menor relacionamento entre tipos diz que um tipo de parâmetro usado em uma posição de "contravariância dupla" (sendo uma entrada de Action, que é em si uma entrada de Meta) se torna covariante. O segundo desfaz a primeira inversão.

No próximo artigo deixaremos os delegates pra trás e falaremos sobre variância nas interfaces.


Comments (0)