IEnumerable<T>
é co-variante, mas não suporta o tipo de valor, apenas o tipo de referência. O código simples abaixo é compilado com sucesso:
IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;
Mas mudar de string
para int
receberá um erro compilado:
IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;
O motivo é explicado no MSDN :
A variação se aplica apenas a tipos de referência; se você especificar um tipo de valor para um parâmetro de tipo variante, esse parâmetro de tipo será invariável para o tipo construído resultante.
Pesquisei e descobri que algumas perguntas mencionavam o motivo do boxe entre o tipo de valor e o tipo de referência . Mas ainda não me lembro muito bem por que o boxe é a razão?
Alguém poderia dar uma explicação simples e detalhada por que covariância e contravariância não suportam o tipo de valor e como o boxe afeta isso?
c#
.net
c#-4.0
covariance
contravariance
cuongle
fonte
fonte
Respostas:
Basicamente, a variação se aplica quando o CLR pode garantir que não precisa fazer nenhuma alteração representacional nos valores. Todas as referências têm a mesma aparência - para que você possa usar um
IEnumerable<string>
como umIEnumerable<object>
sem nenhuma alteração na representação; o próprio código nativo não precisa saber o que você está fazendo com os valores, desde que a infraestrutura tenha garantido que será definitivamente válido.Para tipos de valor, isso não funciona - para tratar um
IEnumerable<int>
comoIEnumerable<object>
, o código que usa a sequência precisaria saber se deveria ser realizada uma conversão de boxe ou não.Você pode ler a publicação no blog de Eric Lippert sobre representação e identidade para obter mais informações sobre esse tópico em geral.
EDIT: Depois de reler a postagem do blog de Eric, é pelo menos tanto identidade quanto representação, embora os dois estejam vinculados. Em particular:
fonte
int
não é um subtipo deobject
. O fato de ser necessária uma mudança representacional é apenas uma consequência disso.Int32
tem duas formas representacionais, "in a box" e "in a box". O compilador precisa inserir código para converter de um formulário para outro, mesmo que isso normalmente seja invisível no nível do código-fonte. De fato, somente o formulário "in a box" é considerado pelo sistema subjacente como um subtipoobject
, mas o compilador lida automaticamente com ele sempre que um tipo de valor é atribuído a uma interface compatível ou a algo do tipoobject
.Talvez seja mais fácil entender se você pensar na representação subjacente (mesmo que esse seja realmente um detalhe de implementação). Aqui está uma coleção de strings:
Você pode pensar
strings
em ter a seguinte representação:É uma coleção de três elementos, cada um sendo uma referência a uma string. Você pode converter isso em uma coleção de objetos:
Basicamente, é a mesma representação, exceto que agora as referências são referências a objetos:
A representação é a mesma. As referências são tratadas apenas de maneira diferente; você não pode mais acessar a
string.Length
propriedade, mas ainda pode ligarobject.GetHashCode()
. Compare isso com uma coleção de ints:Para converter isso em um,
IEnumerable<object>
os dados devem ser convertidos com o encaixe das entradas:Essa conversão requer mais do que um elenco.
fonte
this
refere-se a uma estrutura cujos campos sobrepõem os do objeto de heap que o armazena, em vez de se referir ao objeto que os mantém. Não há uma maneira limpa de uma instância do tipo de valor em caixa obter uma referência ao objeto de heap em anexo.Penso que tudo começa com a definição de
LSP
(Princípio da Substituição de Liskov), que clime:Mas tipos de valor, por exemplo,
int
não podem ser substituídos porobject
inC#
. Prove é muito simples:Isso retorna
false
mesmo se atribuirmos a mesma "referência" ao objeto.fonte
int
não é um subtipo de,object
portanto, o princípio não se aplica. Sua "prova" se baseia em uma representação intermediáriaInteger
, que é um subtipoobject
e para o qual o idioma possui uma conversão implícita (object obj1=myInt;
atualmente é expandida paraobject obj1=new Integer(myInt)
;).int
não é um subtipo deobject
. Além disso, o LSP não se aplica porquemyInt
,obj1
e seobj2
refere a três objetos diferentes: umint
e dois (ocultos)Integer
s.int
palavra-chave do C # é um alias para os BCLsSystem.Int32
, que na verdade é um subtipo deobject
(um alias deSystem.Object
). De fato,int
a classe base éSystem.ValueType
quem é a classe baseSystem.Object
. Tente avaliar a seguinte expressão e veja:typeof(int).BaseType.BaseType
. O motivo deReferenceEquals
retornar falso aqui é que eleint
é encaixotado em duas caixas separadas e a identidade de cada caixa é diferente para qualquer outra caixa. Portanto, duas operações de boxe sempre produzem dois objetos que nunca são idênticos, independentemente do valor do box.System.Int32
ouList<String>.Enumerator
) realmente representa dois tipos de coisas: um tipo de local de armazenamento e um tipo de objeto de pilha (às vezes chamado de "tipo de valor em caixa"). Os locais de armazenamento cujos tipos derivamSystem.ValueType
manterão o primeiro; objetos heap cujos tipos fazem o mesmo manterão o último. Na maioria das línguas, existe um elenco alargado do primeiro para o segundo, e um elenco mais estreito do último para o primeiro. Note-se que, enquanto os tipos de valor em caixa têm o mesmo descritor de tipo como locais-tipo de valor de armazenamento, ...Ele se resume a um detalhe de implementação: Tipos de valor são implementados de maneira diferente para tipos de referência.
Se você forçar os tipos de valor a serem tratados como tipos de referência (por exemplo, encaixotá-los, por exemplo, referindo-se a eles por meio de uma interface), poderá obter variação.
A maneira mais fácil de ver a diferença é simplesmente considerar uma
Array
: uma matriz de tipos de Valor é reunida na memória de forma contígua (diretamente), onde, como uma matriz de tipos de Referência, apenas a referência (um ponteiro) possui contígua na memória; os objetos que estão sendo apontados são alocados separadamente.A outra questão (relacionada) (*) é que (quase) todos os tipos de referência têm a mesma representação para fins de variação e muito código não precisa saber da diferença entre os tipos, portanto, a covariância e a contravariância são possíveis (e facilmente implementado - geralmente apenas por omissão de verificação de tipo extra).
(*) Pode ser o mesmo problema ...
fonte