Eu já havia visto antes, já faz um tempo, e quando vi pensei “Ah, nunca vou ter esse problema.” Pois é, tive. Talvez você também tenha, e como não encontrei muitos recursos em português vai aqui a dica, da maneira mais simplificada que eu consegui:

Herança não funciona com generics. Nem no C#, nem no VB.

- Hã, como assim?

Mais ou menos assim: você tem uma lista de cachorros. Todo cachorro é um mamífero (cachorro herda de mamífero). Você consegue passar uma variável cachorro para uma outra declarada como mamifero:

var cachorro = new Cachorro();

Mamifero mamifero = cachorro;

Nada de mais, certo? E esse código a seguir, funciona? O que você acha?

var cachorros = new List<Cachorro>();

List<Mamifero> mamiferos = cachorros;

Faz sentido que funcionasse, certo? Mas não funciona. Um screenshot do Visual Studio mostra a vocês o erro:

Erro de covariância no Visual Studio (C#)

O erro é “Cannot implicitly convert type 'System.Collections.Generic.List<InvariantsCSharp.Cachorro>' to 'System.Collections.Generic.List<InvariantsCSharp.Mamifero>'”. E nem adianta fazer cast, porque não vai poder. O compilador é muito esperto. E se, de alguma forma, você conseguir, vai dar pau em runtime, na hora da associação. E porquê? Se todo cachorro é um mamífero, uma lista de cachorros é uma lista de mamíferos, certo?

Não. Errado. Na verdade, não é bem uma questão de lógica, mas de especificação. O CLR permitiria isso, quem não permite são o C# e o VB. Primeiro temos que entender que List<Cachorro> não herda de List<Mamifero>. Mesmo se fosse possível, não seria por herança. Entender isso é o primeiro passo.

Em poucas palavras, o motivo disso não ser permitido é o seguinte: Se você pudesse converter uma lista de cachorros para uma lista de mamíferos, poderia, depois, adicionar um gato à lista. E o compilador jamais iria pegar esse problema, você só ia descobrir o problema em tempo de execução. E o grande motivo de existirem os generics é justamente você poder ter segurança do tipo em tempo de compilação. Já imaginou Se isso fosse possível? Dá uma olhada no que poderíamos fazer:

var cachorros = new List<Cachorro>();

List<Mamifero> mamiferos = cachorros;

Gato gato;

mamiferos.Add(gato);

Se isso fosse possível, seria pau na certa. E só em runtime.

Essa especificação, que impede esse tipo de construção, se chama “invariante”. Generics, no C# e no VB, são invariantes.

Mas o problema de listas ainda dá para resolver, só que sem generics. Se você quiser criar uma lista de cachorros, e depois passá-la para uma variável de lista de mamíferos, use um array. A seguinte construção é perfeitamente legal:

Cachorro[] cachorros;

Mamifero[] mamiferos = cachorros;

Agora, já viu se você adicionar um gato nesse array de mamíferos, não é? É pau na hora. O que fizemos foi simplesmente passar um array já criado para outro sendo declarado em um tipo menos específico. Essa possibilidade se chama “covariância”. Também existe a possibilidade de amplicação, chamada contravariância. Penso em possibilidades legítimas de uso, mas tem que ser feito com cuidado. Além disso tudo, arrays são construções que hoje, com os generics, ficaram velhas. Vejam este post do Eric Lippert, feito esta semana, sobre isso. Ele bem nos lembra que arrays não são redimensionáveis, e não são classes, são estruturas, o que nos impede, por exemplo, de deixá-los read-only. Leia o post, é bem legal.

Se a necessidade não for com listas você vai morrer na praia. Não há outra solução. Acreditem, me deparei com isso hoje, e não tem jeito. O compilador não permite em tempo de compilação, e em runtime dá o mesmo pau, se você der um jeito de enganar. Por exemplo, vamos supor esta classe:

class Tranportador<T> where T : Mamifero {}

Ela faz o transporte de mamíferos. Ok, nada de mais. Se você tiver uma função que receba tipado assim:

void Tranportar(Tranportador<Mamifero> transportador) { }

Não consegue fazer isso:

var transportadorDeCachorro = new Tranportador<Cachorro>();

Tranportar(transportadorDeCachorro); //vai dar pau de compilação

Só isso:

var transportadorDeMamifero = new Tranportador<Mamifero>();

Tranportar(transportadorDeMamifero);

Deu para entender o drama?

E como resolve isso? Há uma maneira, mas você vai ter que alterar a função. Fica assim:

static void Tranportar<T>(Tranportador<T> transportador) where T : Mamifero { }

Essa função agora é uma função genérica. Antes a função não era genérica, ela tinha um argumento com o tipo bem definido: Tranportador<Mamifero> transportador. Na prática, a intenção da função anterior está mantida: faça o transporte de algum mamífero.

A chamada à função fica assim:

    1 var transportadorDeCachorro = new Tranportador<Cachorro>();

    2 Tranportar<Cachorro>(transportadorDeCachorro); //sem inferência de tipo

    3 Tranportar(transportadorDeCachorro); //com inferência de tipo

    4 var transportadorDeMamifero = new Tranportador<Mamifero>();

    5 Tranportar<Mamifero>(transportadorDeMamifero); //sem inferência de tipo

    6 Tranportar(transportadorDeMamifero); //com inferência de tipo

Notem que nem precisamos tipar a chamada. Ele já percebe, nas linhas 3 e 6, qual o tipo do método, pelo tipo do argumento.

Vocês já encontraram problemas parecidos? Como resolveram?

Tudo funciona quando você “pensa generics”. Há casos sem solução, mas na prática a lei da invariância é para ajudar.

E se você gostou do assunto, a Wikipedia fala um pouco mais (infelizmente em inglês, sem tradução para o português) e o Rick Byers fez um excelente post sobre o assunto, lá em 2005. Vale a pena dar uma olhada, principalmente no post do Rick. A Wikipedia pega um pouco mais pesado.


Postado na(s) categoria(s) .Net pelo Giovanni Bassi em 26 de setembro de 2008 às 00:03 | Tags: , , ,

Comentários


setembro 26. 2008 08:55
Felipe Pessoto
Sem querer fazer propaganda, mas quando comecei a ler o post logo pensei sobre variancia e os posts do Eric Lipert, que inclusive pedi autorização e traduzi, a série inteira:

blog.fujiy.net/search/label/Covariância%20e%20Contravariância

É um assunto muito interssante mesmo.

http://blog.fujiy.net/http://blog.fujiy.net/


setembro 26. 2008 12:59
Antonio Carlos Zegunis Filho
Quando tive este "problema" criei um Extension Method que transforma uma lista em outra. Não sei se vai aparecer bonitinho, mas segue o código:


public static class ListExtender
    {
        public static List<ToType> ToListType<FromType, ToType>(this List<FromType> source) where ToType : FromType
        {
            List<ToType> newTypeList = new List<ToType>();
            for (int i = 0; i < source.Count; i++)
            {
                ToType myNewTypeItem = (ToType)source[i];
                newTypeList.Add(myNewTypeItem);
            }

            return newTypeList;
        }
    }  

http://blog.tucaz.net/http://blog.tucaz.net/

Comentar


(Vai mostrar seu Gravatar)

  Country flag

biuquote
  • Comentário
  • Pré-visualização
Loading



Quem é Giovanni Bassi

Giovanni Bassi Sou uma pessoa apaixonada por tecnologia e especificamente por .Net. Sou consultor independente especialista em .Net, focado em arquitetura e melhores práticas. Tenho dezenas de artigos publicados na .Net Magazine, revista da qual sou editor técnico. Ministro palestras e cursos de vez em quando, e quando dá tempo eu respiro um pouco. Mais detalhes nesta página.

Busca

Selos

Eu vou ao TechEd Brasil 2010, e você?

MVP

MCPD

MCSD

.Net Magazine

Abaixo ao if!

Calendário

«  setembro 2010  »
seteququsedo
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910
Ver detalhamento de posts no calendário

Blogs interessantes

    OPMLDownload OPML file

    Postagens recentes

    Comentários recentes

    Disclaimer / Aviso
    As opiniões colocadas neste blog são minhas e pessoais e não expressam necessariamente as opiniões de meus empregadores, pareceiros e amigos. Da mesma forma, os comentários feitos por leitores do blog não expressam a minha opinião.

    © Copyright 2010 .Net Unplugged
    Log in