Neden kovaryans ve kontravarlık değer türünü desteklemiyor?


151

IEnumerable<T>bir ko-varyantı ancak değeri türü, sadece tek referans türü desteklemez. Aşağıdaki basit kod başarıyla derlenmiştir:

IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;

Ama değişen stringTo intderlenmiş hatayı alırsınız:

IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;

Nedeni MSDN'de açıklanmıştır :

Varyans yalnızca referans türleri için geçerlidir; Bir varyant türü parametresi için bir değer türü belirtirseniz, bu tür parametresi sonuçta ortaya çıkan yapılandırılmış tür için değişmez.

Araştırdım ve bazı soruların nedeninin değer türü ile referans türü arasında bir karmaşa olduğunu belirttim . Ama neden boks olmanın sebebi hala fikrimi pek açmıyor?

Birisi lütfen basit ve ayrıntılı bir açıklama yapabilir mi kovaryans ve kontravaryans değer türünü desteklemiyor ve boks bunu nasıl etkiliyor?


3
Eric'in benzer soruma verdiği cevaba da bakınız: stackoverflow.com/questions/4096299/…
thorn̈

Yanıtlar:


126

Temel olarak, varyans, CLR değerlerde herhangi bir temsili değişiklik yapmasına gerek olmadığından emin olabildiğinde geçerlidir . Referansların tümü aynı görünür - böylece temsilinde herhangi bir değişiklik olmadan bir an IEnumerable<string>olarak kullanabilirsiniz IEnumerable<object>; Altyapı kesinlikle geçerli olacağını garanti ettiği sürece yerel kodun kendisinin değerlerle ne yaptığınızı bilmesine gerek yoktur.

Değer türleri için bu işe yaramaz - bir'yi bir IEnumerable<int>olarak ele almak için IEnumerable<object>, diziyi kullanan kodun bir boks dönüşümü yapıp yapmayacağını bilmesi gerekir.

Genel olarak bu konuyla ilgili daha fazla bilgi için Eric Lippert'in temsil ve kimlik hakkındaki blog gönderisini okumak isteyebilirsiniz .

DÜZENLEME: Eric'in blog gönderisini kendim okuduktan sonra , ikisi bağlantılı olsa da, en azından temsil kadar kimlik ile ilgili . Özellikle:

Bu nedenle arabirim ve temsilci türlerinin kovaryant ve karşıt değişken dönüşümleri, tüm değişken tür bağımsız değişkenlerin başvuru türlerinde olmasını gerektirir. Bir varyant referans dönüşümünün her zaman kimliği koruduğundan emin olmak için, tür bağımsız değişkenlerini içeren tüm dönüşümlerin de kimliği koruyan olması gerekir. Tür bağımsız değişkenlerindeki önemsiz olmayan tüm dönüştürmelerin kimlik korumalı olduğundan emin olmanın en kolay yolu, bunları referans dönüşümler olarak sınırlamaktır.


5
@CuongLe: Bu bazı açılardan bir uygulama ayrıntısı, ancak kısıtlamanın altında yatan neden sanırım.
Jon Skeet

2
@ AndréCaron: Eric'in blog yazısı burada önemli - bu sadece temsil değil, aynı zamanda kimliğin korunması. Ancak temsilin korunması, üretilen kodun bunu hiç umursamaması anlamına gelir.
Jon Skeet

1
Kesin olarak, kimlik korunamaz çünkü intalt türü değildir object. Temsili bir değişikliğin gerekli olduğu gerçeği, bunun sadece bir sonucudur.
André Caron

3
İnt nasıl bir nesnenin alt türü değildir? Int32, System.Object öğesinden devralan System.ValueType öğesinden miras alır.
David Klempfner

1
@DavidKlempfner Sanırım @ AndréCaron yorumu kötü ifade edilmiş. Int32"Kutulu" ve "kutulu olmayan" olmak üzere iki temsil biçimi vardır gibi herhangi bir değer türü . Derleyici, normalde kaynak kodu seviyesinde görünmez olsa da, bir formdan diğerine dönüştürmek için kod eklemelidir. Aslında, temelde yatan sistem tarafından yalnızca "kutulu" formun bir alt türü olduğu düşünülür object, ancak uyumlu bir arabirime veya bir tür bir şeye bir değer türü atandığında derleyici bununla otomatik olarak ilgilenir object.
Steve

10

Altta yatan temsil hakkında düşünürseniz anlamak belki daha kolaydır (bu gerçekten bir uygulama ayrıntısı olsa bile). İşte dizelerden oluşan bir koleksiyon:

IEnumerable<string> strings = new[] { "A", "B", "C" };

stringsAşağıdaki temsile sahip olduğunu düşünebilirsiniz :

[0]: dize başvurusu -> "A"
[1]: dize başvurusu -> "B"
[2]: dize başvurusu -> "C"

Her biri bir dizeye referans olan üç öğeden oluşan bir koleksiyondur. Bunu bir nesne koleksiyonuna dönüştürebilirsiniz:

IEnumerable<object> objects = (IEnumerable<object>) strings;

Temelde aynı temsildir, ancak artık referanslar nesne referanslarıdır:

[0]: nesne başvurusu -> "A"
[1]: nesne başvurusu -> "B"
[2]: nesne referansı -> "C"

Temsil aynıdır. Referanslar sadece farklı şekilde ele alınır; artık string.Lengthmülke erişemezsiniz, ancak yine de arayabilirsiniz object.GetHashCode(). Bunu bir intler koleksiyonuyla karşılaştırın:

IEnumerable<int> ints = new[] { 1, 2, 3 };
[0]: int = 1
[1]: int = 2
[2]: int = 3

Bunu bir IEnumerable<object>veriye dönüştürmek için , verinin ints kutulanılarak dönüştürülmesi gerekir:

[0]: nesne başvurusu -> 1
[1]: nesne referansı -> 2
[2]: nesne referansı -> 3

Bu dönüşüm, bir dökümden fazlasını gerektirir.


2
Boks sadece bir "uygulama detayı" değildir. Kutulu değer türleri, sınıf nesneleriyle aynı şekilde depolanır ve dış dünyanın anlayabildiği kadarıyla, sınıf nesneleri gibi davranır. Tek fark, kutulu bir değer türü tanımı içinde this, alanları onları tutan nesneye atıfta bulunmak yerine, onu depolayan yığın nesnesinin alanlarını kaplayan bir yapıya atıfta bulunur. Kutulu bir değer türü örneğinin çevreleyen öbek nesnesine bir başvuru almasının temiz bir yolu yoktur.
supercat

7

Bence her şey, tırmanan LSP(Liskov İkame İlkesi) tanımından başlar :

q (x), T türündeki x nesneleri hakkında kanıtlanabilir bir özellikse, q (y), S'nin T'nin bir alt türü olduğu S türündeki y nesneleri için doğru olmalıdır.

Ancak, örneğin değer türleri, in intyerine geçemez . Kanıtlamak çok basit:objectC#

int myInt = new int();
object obj1 = myInt ;
object obj2 = myInt ;
return ReferenceEquals(obj1, obj2);

Nesneye aynı "referansı" atasak falsebile bu geri döner .


1
Doğru prensibi kullandığınızı düşünüyorum ama yapılacak bir kanıt yok: intbir alt tipi olmadığı objectiçin prensip geçerli değil. "Kanıtınız" Integer, bir alt türü olan objectve bunun için dilin örtük bir dönüşümü olan ( object obj1=myInt;aslında genişletilmiş object obj1=new Integer(myInt);) bir ara temsile dayanır .
André Caron

Dil, türler arasında doğru atama ile ilgilenir, ancak int davranışı, nesnenin alt türünden bekleyeceğimiz davranışa karşılık gelmez.
Tigran

Demek istediğim, tam olarak bunun intbir alt türü olmadığı object. Üstelik LSP çünkü geçerli değildir myInt, obj1ve obj2üç farklı nesneler bakın: Bir intve iki (gizli) Integers.
André Caron

22
@ André: C # Java değildir. C # 'ın intanahtar sözcüğü, System.Int32aslında bir alt türü olan object(bir takma adı System.Object) olan BCL'ler için bir takma addır . Aslında,int temel sınıfı System.ValueTypekimin temel sınıfının olduğudur System.Object. Aşağıdaki ifadeyi değerlendirirken deneyin ve görün: typeof(int).BaseType.BaseType. Buradaki ReferenceEqualsyanlış döndürme nedeni int, iki ayrı kutuya yerleştirilmiş olması ve her kutunun kimliğinin diğer herhangi bir kutu için farklı olmasıdır. Bu nedenle, iki kutulama işlemi, kutulu değerden bağımsız olarak her zaman hiçbir zaman aynı olmayan iki nesne verir.
Allon Guralnek

@AllonGuralnek: Her bir değer türü (örneğin System.Int32veya List<String>.Enumerator) aslında iki tür şeyi temsil eder: bir depolama konumu türü ve bir yığın nesne türü (bazen "kutulu değer türü" olarak adlandırılır). Türleri türetilen depolama yerleri ilkini System.ValueTypetutacaktır; türleri aynı şekilde olan yığın nesneleri ikinciyi tutacaktır. Çoğu dilde, birincisinde genişleyen bir kalıp ve ikinciden birinciye daralan bir kalıp vardır. Kutulu değer türleri, değer türü depolama konumlarıyla aynı tür tanımlayıcısına
sahipken

3

Bir uygulama ayrıntısına iner: Değer türleri, referans türlerinden farklı şekilde uygulanır.

Değer türlerini referans türleri olarak ele alınmaya zorlarsanız (örneğin, bir arabirim aracılığıyla onlara başvurarak onları kutuya alırsanız) varyans elde edebilirsiniz.

Farkı görmenin en kolay yolu basitçe şunu düşünmektir Array: bir Değer türleri dizisi, bellekte bitişik (doğrudan) olarak bir araya getirilir, burada bir Referans türleri dizisi yalnızca bellekte bitişik olarak başvuruya (bir işaretçi) sahiptir; işaret edilen nesneler ayrı olarak tahsis edilir.

Diğer (ilgili) sorun (*), (hemen hemen) tüm Referans türlerinin varyans amaçları için aynı gösterime sahip olması ve çoğu kodun türler arasındaki farkı bilmesine gerek olmamasıdır, bu nedenle birlikte ve ters varyans mümkündür (ve kolayca uygulanır - genellikle sadece ekstra tip kontrolünün ihmal edilmesiyle).

(*) Aynı sorun olarak görünebilir ...

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.