Covariância e Contravariância em C#, Parte Seis:Variância de Interface

05/14/2008 16:12:00 By Felipe Pessoto

Nos posts passados nós discutimos como seria possível tratar um delegate como contravariante nos seus argumentos e covariante no seu tipo de retorno. Um delegate é basicamente um objeto que representa uma chamada de função. Nós podemos fazer estes mesmos tipos de coisas para outros que representam chamadas de funções. Interfaces, por exemplo, são contratos que especificam qual o conjunto de chamadas de função que estão disponíveis em um objeto em particular.

Isto quer dizer que nós devemos extender a noção de variância para definições de interface também, usando as mesmas regras que nós temos para delegates. Por exemplo, considere:

public interface IEnumerator<T> : IDisposable, IEnumerator
{
    new T Current { get; }
}

Aqui nós temos uma interface genérica onde o único uso de parâmetro está em uma posição de saída. Poderíamos, assim, considerar o parâmetro covariante. Isso significaria que seria válido atribuir um objeto que implementa IEnumerator<Girafa> para uma variável do tipo IEnumerator<Animal>. Desde que o usuário daquela variável irá sempre esperar um Animal, e a nossa implementação irá sempre produzir uma Girafa, fica tudo ok.

Então temos IEnumerator<+T>, então nós podemos perceber que IEnumerable<T> é definido como:

public interface IEnumerable<T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}

Novamente, o parâmetro aparece somente em uma posição de saída, então nós poderiamos ter IEnumerable<+T> covariante também.

Isto então abre uma janela de assassinatos de bons cenários. Hoje, este código deve falhar na compilação:

void AlimentarAnimais(IEnumerable<Animal> animals)
{
    foreach (Animal animal in animals)
        if (animal.Faminto)
            Alimentar(animal);
}
...
IEnumerable<Girafa> GirafasAdultas = from g in girafas where g.Age > 5 select g;
AlimentarAnimais(GirafasAdultas);

Porque GirafasAdultas implementa IEnumerable<Girafa>, não IEnumerable<Animal>. No C# 3.0 você tem que fazer um idiota e dispendiosa operação de conversão para fazer isto compilar, algo como:

AlimentarAnimais(GirafasAdultas.Cast<Animal>());

ou

AlimentarAnimais(from g in GirafasAdultas select (Animal)g);

Ou seja o que for. Esta declaração explícita não deveria ser necessária. Diferente de arrays (que são leitura-escrita) é perfeitamente seguro tratar uma lista de Girafas read-only como uma lista de Animais.

Do mesmo modo, poderíamos tornar:

public interface IComparer<-T>
{
    int Compare(T x, T y);
}

uma interface contravariante, desde que o tipo fosse usado somente em posições de entrada. Você poderia, então, implementar um objeto que compara dois Animais e usá-lo em um contexto onde você precisa de um objeto que compare duas Girafas sem se preocupar com problemas no sistema de tipos.


Comments (0)