Welcome to GASP Sign in | Join | Help

Paulo Morgado

Tudo sobre Arquitectura de Software

Localização dos Visitantes

  • Localização dos Visitantes

Livros

  • LINQ com C#

Eventos

Renûncia

As opiniões e pontos de vista expressos neste sítio são minhas e podem não reflectir as da Microsoft, do meu empregador, ou de qualquer comunidade a que pertença. Qualquer código ou opinião é oferecido sem qualquer garantia. Os produtos ou serviços mencionados são comprados por mim, disponibilizados pelo meu empregador ou pelo fabricante/vendedor o que não influencia em nada a minha opinião.

C# 4.0: Covariância E Contravariância Em Genéricos

O C# 4.0 (e a .NET 4.0) introduziram covariância e contravariância em interfaces e delegates genericos. Mas afinal o que é a variância?

Segundo a Wikipedia, em álgebra multilinear, covariância e contravariância descrevem como a descrição quantitativa de certas entidades geométricas ou físicas variam quando passam de um systema de coordenadas para outro.(*)

Mas o que é que isto tem a ver com C# ou .NET?

Na teoria de tipos, um tipo T é maior (>) que o tipo S se S é um subtipo (deriva) de T, o que quer dizer que existe uma descrição quantitativa para tipos numa hierarquia de tipos.

Sendo assim, como é que a covariância e a contravariância se aplicam aos tipos genéricos do C# (e de .NET)?

Em C# (e em .NET), variância é uma relação entre uma definição de tipo genérico e um determinado tipo parâmetro de genéricos.

Dados dois tipos Base e Derivado, em que:

  • Existe uma conversão por referência (ou identidade) entre Base e Derivado
  • Base Derivado

Uma definição de tipo genérico Genérico<T> é:

  • covariante em T se a ordem dos tipos construídos segue a ordem dos tipos parâmetros de genéricos : Genérico<Base> ≥ Genérico<Derivado>

  • contravariante em T se a ordem dos tipos construídos é inversa à ordem dos tipos parâmetros de genéricos : Genérico<Base> Genérico<Derivado>
  • invariante em T se nenhuma das regras acima se aplica.

Se aplicarmos a definição a arrays, podemos constatar que os arrays sempre foram covariantes porque este código é válido:

object[] objectArray = new string[] { "string 1", "string 2" };
objectArray[0] = "string 3";
objectArray[1] = new object();

Contudo, quando tentamos correr este código, a segunda afectação vai lançar uma ArrayTypeMismatchException. Apesar do compilador ter sido enganado para pensar que o código era válido porque um object está a ser atribuído a um elemento de um array de object, em tempo de execução, há sempre uma verificação de tipos para garantir que o tipo em tempo de execução da definição dos elementos do array é maior ou igual ao da instância quie está a ser atribuída ao elemento. No exemplo acima, porque o tipo em tempo de execução é array de string, a primeira afectação de elementos é válida porque string ≥ string e a segunda não é válida porque string ≤ object.

Isto leva-nos à conclusão de que, apesar dos arrays sempre terem sido covariantes, não são covariantes sem riscos – não é garantido que código que compila corra sem erros.

Em C#, a variância em relação  um determinado tipo parâmetro de genéricos é forçada na declaração e não determinada pelo uso desse tipo parâmero de genéricos.

A covariância em relação a um determinado tipo parâmetro de genéricos é forçada através do modificador genérico out:

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

public interface IEnumerator<out T>
{
    T Current { get; }
    bool MoveNext();
}

Note-se o conveniente uso da palavra reservada out já existente. Além do benefício de não nos termos de lembrar de uma nova hipotética palavra reservada covariant, out (saída) é mais fácil de lembrar porque define que o tipo parâmetro de genéricos apenas pode ser usando em posições de saída — propriedades apenas de leitura e valores de saída de métodos.

Similarmente, a contravariância em relação a um determinado tipo parâmetro de genéricos é forçada através do modificador genérico in:

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

Mais uma vez, o uso da palavra reservada in (entrada) já existente é mais fácil de lembrar keyporque define que o tipo parâmetro de genéricos apenas pode ser usando em posições de entrada — propriedades apenas de escrita e parâmetros de métodos que não sejam por referência (ref) nem de saída (out).

Um tipo parâmetro de genéricos que não seja marcado covariante (out) ou contravariante (in) é invariante.

Porque a variância se aplica à relação entre uma definição de tipo genérico e um determinado tipo parâmetro de genéricos, uma definição de tipo genérico pode ser covariante, contravariante, invariante dependendo do tipo parâmetro genérico.

public delegate TResult Func<in T, out TResult>(T arg);

Na definição do delegate genérico acima, Func<T, TResult> é contravariant em T e convariante em TResult.

Todos os tipos da .NET Framework onde a variância podia ser aplicada foram alterados para que os seus tipos parâmetros de genéricos pudessem tirar partido desta funcionalidade.

Em resumo, as regras da variância em C# (e .NET) são:

  • A variância em relação a tipos parâmetros de genéricos está restringida aos tipos interfaces genéricas e delegates genéricos.
  • Uma interface genérica ou um delegate genérico podem ser, em simultâneo covariante, contravariante ou invariante em relação a diferentes tipos parâmetros de genéricos.
  • A variância aplica-se apenas a tipos referência: um IEnumerable<int> não é um IEnumerable<object>.
  • A variância não se aplica à combinação de delegates. Isto é, dados dois delegates do tipo Action<Derived> e Action<Base>, não é possível combinar o segundo delegate com o primeiro apesar de que o resulado ser seguro do ponto de vista do tipo. A variância permite que o segundo delegate seja atribuído a uma variável do tipo Action<Derived>, mas os delegates apenas podem ser combinados se os seus tipos forem uma correspondência exacta.

Quem desejar aprender mais acerca da variância em C# (e .NET), pode ler:

Nota: Por a variância ser uma funcionalidade da .NET 4.0 e não apenas do C# 4.0, estes conceitos também se aplicam ao Visual Basic 10.

Posted: Tuesday, April 13, 2010 2:26 AM by Paulo Morgado

Comments

Paulo Morgado said:

Numa entrada anterior entrada, falei sobre como os arrays são covariantes em relação ao tipo dos seus elementos, mas que essa variância não é segura.

# August 2, 2010 1:23 AM
Anonymous comments are disabled