Variância e Contra-variância: uma dívida
Tenho que pagar uma dívida aqui. Falei na volta do PDC que iria falar sobre variância e contra-variância, pois haverá mudanças no C# 4.0. Bem, aqui vai...
Antes de tudo, vou tentar ser simples e lidar com o assunto sem muitos detalhes. Quem quiser saber mais, recomendo a séries de blogs do Eric Lippert aqui. A definição de variância e contra-variância pode ser encontrada aqui.
Variância e contra-variância tem tudo a ver com atribuição entre tipos e sub-tipos. Como diz o comentarista chato da TV, “a regra é clara” – uma atribuição de um objeto de um tipo B para outro do tipo A só é válida se A>= B (A é super-classe ou da mesma classe que B). Assim,
Animal a = new Mamifero(); é válido
Mamifero m = a; é inválido !
Note que isto também tem que ser válido em outras partes da linguagem onde a atribuição está escondida. Por exemplo, no caso da passagem de parâmetros que é, de fato, uma atribuição.
Existem 4 possibilidades básicas numa passagem de parâmetro: cópia do argumento na entrada (parâmetro in), cópia para o argumento na saída (out), cópia do/para o argumento (inout) e cópia da referência (ref). Podemos encarar todas estas cópias como atribuições e uma linguagem que lida com tipos deve evitar passagens inválidas. Assim, tendo definido o método
void M( in Mamifero p1, out Animal p2) {...}
não poderemos usar um Animal para p1 ou um Mamifero para p2 como argumentos na chamada do método M. A regra de atribuição seria quebrada.
Vamos adicionar agora a herança como mais uma dimensão neste jogo. Imagine as seguintes classes:
class A {
public void M( in Mamifero p1, out Animal p2) {…}
}
class B: A {
public override void M( in Mamifero p1, out Animal p2) {…}
}
Existe aqui uma oportunidade de sermos mais precisos que, porém, nem todas as linguagens nos permitem. Se as linguagens nos deixassem, poderíamos ser mais precisos e rigorosos na declaração dos parâmetros do método M da classe B em dois sentidos:
1) O parâmetro p1 poderia ter seu tipo amplificado, para Animal, por exemplo (aceita-se a partir de agora um tipo mais amplo);
2) O p2 poderia se tornar mais restritivo, como, por exemplo, Mamifero (limitamos, a partir de agora, a saída para um tipo mais restrito);
Portanto, a definição
class B: A {
public override void M( in Animal p1, out Mamifero p2) {…}
}
deveria ser plausível numa linguagem orientada a objetos, pois ainda estaria respeitando a movimentação dos argumentos segundo os tipos. Note também que esta restrição/ampliação de tipos dos parâmetros não é uma obrigatoriedade, mas sim uma oportunidade que pode ser usada caso haja sentido, isto é, caso a semântica do método M da classe B permitam esta restrição/ampliação.
Neste contexto, a ampliação/restrição dos tipos dos parâmetro são chamadas variância (as vezes chamada de co-variância) e contra-variância, pois uma acompanha o aumento da restrição que a subclasse já impõe, enquanto a outra vai no sentido oposto.
Existem várias outras dimensões onde esta regra tem importância, como delegação, tipos genéricos e tipos que são conjuntos de outros tipos (ex.: arrays de Mamíferos). Minha intenção aqui não é a de descrever o impacto desta regra em todos os mecanismos lingüísticos, mas sim de dar uma base para que possamos entender as futuras mudanças do C# 4.0 quanto a este tema.
Abraços e me desculpem o blog longo.
Comments
- Anonymous
November 29, 2008
PingBack from http://blog.a-foton.ru/index.php/2008/11/29/variancia-e-contra-variancia-uma-divida/