Pergunta

Variação genérico em C # 4.0 foi implementado de tal forma a que é possível escrever o seguinte, sem uma exceção (que é o que aconteceria em C # 3.0):

 List<int> intList = new List<int>();
 List<object> objectList = intList; 

[Exemplo de não-funcionais: resposta See de Jon Skeet]

Recentemente assisti a uma conferência onde Jon Skeet deu uma excelente visão geral de variância genérico, mas eu não tenho certeza que estou recebendo-lo completamente - Eu entendo o significado das palavras-chave in e out quando se trata de contrapartida e co -variance, mas estou curioso para o que acontece nos bastidores.

O que a CLR ver quando esse código é executado? É implicitamente converter o List<int> para List<object> ou é simplesmente construído em que agora podemos converter entre tipos derivados de tipos de pais?

Fora de interesse, por que não foi este introduzido em versões anteriores e que é o principal benefício? - ou seja, o uso mundo real

Mais informação sobre este pós para a variância genérico (mas questão é extremamente desatualizado, olhando para o real, up-to-date informações)

Foi útil?

Solução

Não, seu exemplo não iria funcionar por três razões:

  • Classes (tais como List<T>) são invariantes; apenas delegados e interfaces são variante
  • Para variância ao trabalho, a interface tem de usar apenas o parâmetro de tipo em uma direção (in para contravariance, para fora para covariância)
  • Tipos de valor não são suportados como argumentos de tipo para variância - por isso não há converstion de IEnumerable<int> para IEnumerable<object> por exemplo

(O código não compilar em C # 3.0 e 4.0 -. Não há nenhuma exceção)

Portanto, este faria trabalho:

IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

O CLR apenas usa a referência, inalterado - há novos objetos são criados. Então, se você chamada objects.GetType() você ainda deseja obter List<string>.

Eu acredito que não foi introduzido no início porque os projetistas da linguagem ainda tinha que trabalhar os detalhes de como para expô-lo. - É sido no CLR desde v2

Os benefícios são os mesmos que outros momentos em que você quer ser capaz de usar um tipo como outro. Para usar o mesmo exemplo que eu usei no último sábado, se você tem algo implementos IComparer<Shape> para comparar formas de área, é uma loucura que você não pode usar isso para classificar uma List<Circle> - se é que se pode comparar quaisquer duas formas, certamente pode comparar quaisquer dois círculos. A partir de C # 4, haveria uma conversão contravariant de IComparer<Shape> para IComparer<Circle> assim que você poderia chamar circles.Sort(areaComparer).

Outras dicas

Alguns pensamentos adicionais.

O que a CLR ver quando esse código é executado

Como Jon e outros observaram corretamente, não estamos fazendo variância em classes, apenas interfaces e delegados. Assim, no seu exemplo, o CLR não vê nada; que o código não compilar. Se você forçá-lo a compilar inserindo moldes suficientes, ele trava em tempo de execução com uma exceção de elenco ruim.

Agora, ainda é uma pergunta razoável para perguntar como variância trabalha nos bastidores quando ela não funciona. A resposta é: a razão pela qual estão a restringir isso para argumentos de tipo de referência que a interface parameterize e tipos de delegado é para que nada acontece nos bastidores. Quando você diz

object x = "hello";

o que acontece nos bastidores é a referência para a cadeia é preso na variável de tipo de objeto sem modificação . Os bits que compõem uma referência a uma seqüência são bits legais para ser uma referência a um objeto, de modo que nada precisa acontecer aqui. O CLR simplesmente pára de pensar desses bits como se referindo a uma corda e começa a pensar neles como referindo-se a um objeto.

Quando você diz:

IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = e1;

A mesma coisa. Nada acontece. Os bits que fazem uma ref a um recenseador corda são os mesmos que os bits que formam uma referência a um recenseador objeto. Há um pouco mais mágica que entra em jogo quando você faz um elenco, dizer:

IEnumerator<string> e1 = whatever;
IEnumerator<object> e2 = (IEnumerator<object>)(object)e1;

Agora, o CLR deve gerar um cheque que e1 realmente não implementar essa interface, e que o check tem que ser esperto sobre reconhecendo variância.

Mas a razão que podemos ir longe com interfaces variantes de estar apenas a não-op conversões é , porque compatibilidade regular de atribuição é assim. O que você vai usar e2 para?

object z = e2.Current;

que retorna bits que são uma referência a uma string. Nós já estabelecemos que aqueles são compatíveis com o objeto sem alterações.

Por que não foi isso introduzido no início? Tivemos outras características para fazer e um orçamento limitado.

Qual é a vantagem princípio? Que as conversões de seqüência de seqüência para seqüência de objeto "apenas trabalho".

Fora de interesse, porque não era essa introduzido em versões anteriores

As primeiras versões (1.x) do .NET não têm genéricos em tudo, assim variância genérico era muito longe.

Note-se que, em todas as versões do .NET, há variedade de covariância. Infelizmente, é covariância inseguro:

Apple[] apples = new [] { apple1, apple2 };
Fruit[] fruit = apples;
fruit[1] = new Orange(); // Oh snap! Runtime exception! Can't store an orange in an array of apples!

O co e contra-variância em C # 4 é seguro e impede que este problema.

o que é o principal benefício - isto é verdadeiro uso do mundo?

Muitas vezes em código, você está chamando uma API espera um tipo amplificado de Base (por exemplo IEnumerable<Base>), mas tudo que você tem é um tipo amplificado de Derived (por exemplo IEnumerable<Derived>).

Em C # 2 e C # 3, você precisa converter manualmente para IEnumerable<Base>, mesmo que ele deveria "apenas trabalho". Co e contra-variância torna "apenas trabalho".

P.S. Suga totalmente que a resposta de Skeet é comer todos os meus pontos de rep. Maldito seja, Skeet! :-) Parece que ele está respondeu isso antes , no entanto.

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