Pergunta

Alguém pode me explicar, o conceito de covariância e / contravariância no linguagens de programação teoria?

Foi útil?

Solução

Covariância é bastante simples e melhor pensado da perspectiva de alguma aula de coleta List. Podemos parametrize a List classe com algum parâmetro de tipo T. Isto é, nossa lista contém elementos do tipo T para alguns T. Lista seria covariável se

S é um subtipo de T IFF List [s] é um subtipo da lista [T

(Onde estou usando a definição matemática iff significar se e apenas se.)

Aquilo é um List[Apple] é um List[Fruit]. Se houver alguma rotina que aceite um List[Fruit] como um parâmetro, e eu tenho um List[Apple], então posso passar isso como um parâmetro válido.

def something(l: List[Fruit]) {
    l.add(new Pear())
}

Se nossa aula de coleção List é mutável, então a covariância não faz sentido, porque podemos assumir que nossa rotina poderia adicionar outras frutas (que não era uma maçã) como acima. Portanto, devemos gostar apenas imutável Aulas de coleção para serem covariantes!

Outras dicas

Aqui estão meus artigos sobre como adicionamos novos recursos de variação ao C# 4.0. Comece de baixo.

http://blogs.msdn.com/ericlippert/archive/tags/covarince+and+contravariance/default.aspx

Há uma distinção a ser feita entre covariância e contravariância.
Muito mais ou menos, uma operação é covariante se preserva a ordem dos tipos, e contravariantes se inverte esta ordem.

O pedido em si, é utilizado para representar mais tipos gerais como maior de tipos mais específicos.
Aqui está um exemplo de uma situação onde o C# suporta covariância.Primeiro, esta é uma matriz de objetos:

object[] objects=new object[3];
objects[0]=new object();
objects[1]="Just a string";
objects[2]=10;

É claro que é possível inserir diferentes valores para a matriz, porque no final todos eles derivam de System.Object em .Net framework.Em outras palavras, System.Object é muito geral, ou grande escreva.Agora aqui é um lugar em que a covariância é suportado:
atribuir um valor de um tipo menor a uma variável de um tipo maior

string[] strings=new string[] { "one", "two", "three" };
objects=strings;

O objetos de variável, que é do tipo object[], pode armazenar um valor que é, na verdade, do tipo string[].

Pense sobre isso — para um ponto, é o que você esperava, mas, novamente, não.Afinal, enquanto string deriva de object, string[] NÃO derivam de object[].O suporte de idioma para a covariância neste exemplo faz a atribuição de qualquer maneira possível, que é algo que você vai encontrar em muitos casos. Variância é um recurso que faz a língua funcionar de forma mais intuitiva.

As considerações em torno desses temas são extremamente complicado.Por exemplo, com base no código anterior, aqui estão dois cenários que vão resultar em erros.

// Runtime exception here - the array is still of type string[],
// ints can't be inserted
objects[2]=10;

// Compiler error here - covariance support in this scenario only
// covers reference types, and int is a value type
int[] ints=new int[] { 1, 2, 3 };
objects=ints;

Um exemplo para o funcionamento do contravariância é um pouco mais complicado.Imagine estes duas classes:

public partial class Person: IPerson {
    public Person() {
    }
}

public partial class Woman: Person {
    public Woman() {
    }
}

Woman é derivado a partir de Person, obviamente.Agora, considere que você tem essas duas funções:

static void WorkWithPerson(Person person) {
}

static void WorkWithWoman(Woman woman) {
}

Uma das funções faz algo(não importa qual) com um Woman, o outro é mais geral e pode trabalhar com qualquer tipo de derivado de Person.No Woman do lado das coisas, agora você também tem esses:

delegate void AcceptWomanDelegate(Woman person);

static void DoWork(Woman woman, AcceptWomanDelegate acceptWoman) {
    acceptWoman(woman);
}

DoWork é uma função que pode levar um Woman e uma referência a uma função que leva um Woman, e , em seguida, ele passa a instância do Woman para o delegado.Considere o polimorfismo os elementos que você tem aqui. Person é maior de Woman, e WorkWithPerson é maior de WorkWithWoman. WorkWithPerson também é considerado maior de AcceptWomanDelegate com a finalidade de variância.

Finalmente, você tem estas três linhas de código:

Woman woman=new Woman();
DoWork(woman, WorkWithWoman);
DoWork(woman, WorkWithPerson);

Um Woman instância é criada.Em seguida, DoWork é chamado, passando o Woman instância, bem como uma referência para o WorkWithWoman o método.O último é, obviamente, compatível com o tipo de delegado AcceptWomanDelegate — um parâmetro do tipo Woman, nenhum tipo de retorno.A terceira linha é um pouco estranho, no entanto.O método WorkWithPerson toma uma Person como parâmetro, e não um Woman, como exigido pelo AcceptWomanDelegate.No entanto, WorkWithPerson é compatível com o tipo de delegado. Contravariância torna possível, então, no caso dos delegados, a maior do tipo WorkWithPerson pode ser armazenado em uma variável do tipo menor AcceptWomanDelegate.Mais uma vez é o intuitivo: se WorkWithPerson pode trabalhar com qualquer Person, passando um Woman não pode ser errado, certo?

Por agora, você pode estar se perguntando como tudo isso se relaciona com os genéricos.A resposta é que a variância pode ser aplicado para os genéricos também.O exemplo anterior usado object e string matrizes.Aqui, o código utiliza listas genéricas em vez de matrizes:

List<object> objectList=new List<object>();
List<string> stringList=new List<string>();
objectList=stringList;

Se você tentar fazer isso, você vai descobrir que este não é um cenário suportado em C#.Na versão em C# 4.0, bem como .Net framework 4.0, desvio de apoio em medicamentos genéricos tem sido limpos, e agora é possível usar as novas palavras-chave no e fora com parâmetros de tipo genérico.Eles podem definir e restringir a direção do fluxo de dados para um determinado tipo de parâmetro, permitindo o desvio para o trabalho.Mas, no caso de List<T>, os dados do tipo T flui em ambos os sentidos — existem métodos do tipo List<T> que retorno T valores, e outros que receber tais valores.

O ponto de estas restrições é direcional para permitir o desvio de onde faz sentido, mas para evitar problemas como o erro de tempo de execução mencionado em uma de matriz anterior exemplos.Quando digita os parâmetros corretamente são decorados com no ou fora, o compilador pode verificar, e permitir ou não, a sua variação em tempo de compilação.A Microsoft tem ido para o esforço de adicionar essas palavras-chave para muitos de interfaces padrão em .Net framework, como IEnumerable<T>:

public interface IEnumerable<out T>: IEnumerable {
    // ...
}

Para esta interface, o fluxo de dados do tipo T objetos é clara: eles podem apenas ser obtidos a partir de métodos suportados por esta interface, não é passada para eles.Como resultado, é possível construir-se um exemplo semelhante ao List<T> tentativa descrito anteriormente, mas usando IEnumerable<T> :

IEnumerable<object> objectSequence=new List<object>();
IEnumerable<string> stringSequence=new List<string>();
objectSequence=stringSequence;

Este código é aceitável para o compilador C# desde a versão 4.0, uma vez que IEnumerable<T> é covariante devido à fora especificador de tipo de parâmetro T.

Ao trabalhar com tipos genéricos, é importante estar ciente de variância e a maneira como o compilador é a aplicação de vários tipos de artifícios para fazer o seu código funcionar da maneira que você esperava.

Há muito mais para saber sobre o desvio do que é abordado neste capítulo, mas isso será suficiente para fazer todo o código mais compreensível.

Ref:

Bart de Smet tem uma ótima entrada de blog sobre covariância e violação aqui.

C# e o CLR permitir covariância e contra-variação de tipos de referência quando a ligação de um método para um delegado.Covariância significa que um método pode retornar um tipo que é derivada do delegado, do tipo de retorno.Contra-variância significa que um método pode levar um parâmetro que é uma base de o delegado tipo de parâmetro.Por exemplo, dado um delegado definido assim:

delegado Objeto MyCallback(FileStream s);

é possível construir-se um exemplo deste tipo de delegado vinculado a um método que é protótipo

como esta:

Cadeia SomeMethod(Fluxo s);

Aqui, SomeMethod tipo do retorno da (Seqüência de caracteres) é um tipo que é derivada do delegado tipo de retorno (Objeto);este covariância é permitido.SomeMethod do tipo de parâmetro (Stream) é um tipo que é uma classe base do delegado do tipo de parâmetro (FileStream);este contra-variância é permitido.

Observe que a covariância e contra-variância são suportados apenas para tipos de referência, não para tipos de valor ou para o vazio.Assim, por exemplo, eu não posso ligar o método a seguir para o MyCallback delegado:

Int32 SomeOtherMethod(Fluxo s);

Apesar de SomeOtherMethod tipo do retorno da (Int32) é derivada de MyCallback retorno tipo (Objeto), esta forma de covariância não é permitido porque Int32 é um tipo de valor.

Obviamente, a razão pela qual o valor de tipos e vazio não pode ser utilizado para a covariância e contra-variância é porque a estrutura de memória para essas coisas varia, considerando que a estrutura de memória para tipos de referência é sempre um ponteiro.Felizmente, o compilador C# vai produzir um erro se você tentar fazer algo que não é suportado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top