Pesquisar

25 de abr de 2008

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

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.

Continua na parte seis: Covariância e Contravariância em C#, Parte Seis:Variância de Interface

Parte Anterior: Covariância e Contravariância em C#, Parte Quatro: Variância de Delegate Real

Nenhum comentário: