C # 'da Kovaryant ve Contravariant arayüzlerini anlama


87

Bunlara C # üzerinde okuduğum bir ders kitabında rastladım, ancak muhtemelen bağlam eksikliğinden dolayı bunları anlamakta güçlük çekiyorum.

Orada ne oldukları ve ne işe yaradıkları hakkında iyi ve kısa bir açıklama var mı?

Açıklama için düzenleyin:

Kovaryant arayüz:

interface IBibble<out T>
.
.

Kontravariant arayüzü:

interface IBibble<in T>
.
.

3
Bu kısa ve iyi bir açıklamadır IMHO: blogs.msdn.com/csharpfaq/archive/2010/02/16/…
digEmAll

Yararlı olabilir: Blog
Gönderisi

Hmm bu iyi ama beni gerçekten şaşırtan şeyin neden olduğunu açıklamıyor .
NibblyPig

Yanıtlar:


144

İle <out T>, arayüz referansını hiyerarşide yukarı doğru bir referans olarak ele alabilirsiniz.

İle <in T> , arayüz referansını hiearchy'de aşağı doğru bir referans olarak ele alabilirsiniz.

Bunu daha ingilizce terimlerle açıklamaya çalışayım.

Diyelim ki hayvanat bahçenizden hayvanların bir listesini alıyorsunuz ve onları işlemeyi düşünüyorsunuz. Tüm hayvanların (hayvanat bahçenizdeki) bir adı ve benzersiz bir kimliği vardır. Bazı hayvanlar memeliler, bazıları sürüngenler, bazıları amfibiler, bazıları balık, vs. ama hepsi hayvan.

Bu nedenle, (farklı türdeki hayvanları içeren) hayvan listenizle, tüm hayvanların bir adı olduğunu söyleyebilirsiniz, bu nedenle tüm hayvanların adını almanız kesinlikle güvenli olacaktır.

Ancak, ya yalnızca balıkların bir listesine sahipseniz ve onlara hayvan gibi davranmanız gerekiyorsa, bu işe yarıyor mu? Sezgisel olarak çalışmalıdır, ancak C # 3.0 ve öncesinde bu kod parçası derlenmeyecektir:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>

Bunun nedeni derleyici size niyetinde ne "bilmez" ya olmasıdır olabilir bunu aldıktan sonra, hayvanlar koleksiyonu ile yapmak. Tek bildiği gibi, IEnumerable<T>bir nesneyi listeye geri koymanın bir yolu olabilir ve bu, balık olmayan bir hayvanı yalnızca balık içermesi gereken bir koleksiyona koymanıza olanak tanır.

Başka bir deyişle, derleyici buna izin verilmediğini garanti edemez:

animals.Add(new Mammal("Zebra"));

Dolayısıyla derleyici, kodunuzu derlemeyi tamamen reddeder. Bu kovaryans.

Kontravariance bakalım.

Hayvanat bahçemiz tüm hayvanları idare edebildiğinden, kesinlikle balıkları idare edebilir, bu yüzden hayvanat bahçemize biraz balık eklemeye çalışalım.

C # 3.0 ve öncesinde, bu derlemez:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));

Burada, derleyici olabilir yöntem döndürür rağmen Bu kod parçasını izin List<Animal>sadece bu tip değişti eğer öyleyse, bütün balıklar hayvanlardır çünkü basitçe:

List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));

O zaman işe yarar, ancak derleyici bunu yapmaya çalışmadığınızı belirleyemez:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];

Liste aslında bir hayvan listesi olduğu için buna izin verilmiyor.

Dolayısıyla, ters ve eş varyans, nesne referanslarını nasıl ele aldığınız ve onlarla ne yapmanıza izin verildiğidir.

inVe outbir ya da diğer olarak C anahtar # 4,0 spesifik işaretler arayüzü. İle in, genel türü (genellikle T) girdi konumlarına yerleştirmenize izin verilir ; bu, yöntem bağımsız değişkenleri ve salt yazılır özellikler anlamına gelir.

İle out, genel türü , yöntem dönüş değerleri, salt okunur özellikler ve çıkış yöntemi parametreleri olan çıktı konumlarına yerleştirmenize izin verilir .

Bu, kodla ne yapmak istediğinizi yapmanıza izin verecektir:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
// since we can only get animals *out* of the collection, every fish is an animal
// so this is safe

List<T> T üzerinde hem giriş hem de çıkış yönüne sahiptir, bu nedenle ne ortak değişken ne de ters varyanttır, ancak aşağıdaki gibi nesneler eklemenize izin veren bir arayüzdür:

interface IWriteOnlyList<in T>
{
    void Add(T value);
}

bunu yapmanıza izin verir:

IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
                                                            IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe

İşte kavramları gösteren birkaç video:

İşte bir örnek:

namespace SO2719954
{
    class Base { }
    class Descendant : Base { }

    interface IBibbleOut<out T> { }
    interface IBibbleIn<in T> { }

    class Program
    {
        static void Main(string[] args)
        {
            // We can do this since every Descendant is also a Base
            // and there is no chance we can put Base objects into
            // the returned object, since T is "out"
            // We can not, however, put Base objects into b, since all
            // Base objects might not be Descendant.
            IBibbleOut<Base> b = GetOutDescendant();

            // We can do this since every Descendant is also a Base
            // and we can now put Descendant objects into Base
            // We can not, however, retrieve Descendant objects out
            // of d, since all Base objects might not be Descendant
            IBibbleIn<Descendant> d = GetInBase();
        }

        static IBibbleOut<Descendant> GetOutDescendant()
        {
            return null;
        }

        static IBibbleIn<Base> GetInBase()
        {
            return null;
        }
    }
}

Bu işaretler olmadan aşağıdakiler derlenebilir:

public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant

veya bu:

public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
                                               as Descendants

Hmm, kovaryans ve kontravansın amacını açıklayabilir misin? Daha fazla anlamama yardımcı olabilir.
NibblyPig

1
Son bit'e bakın, derleyicinin daha önce engellediği şey, giriş ve çıkışın amacı güvenli olan arayüzlerle (veya türlerle) neler yapabileceğinizi söylemektir, böylece derleyici güvenli şeyler yapmanızı engellemeyecektir. .
Lasse V.Karlsen

Harika cevap, çok yardımcı oldukları videoları izledim ve örneğinizle birleştirerek şimdi sıraladım. Geriye sadece bir soru kalıyor ve bu neden 'dışarıda' ve 'içeri' gerekli, neden görsel stüdyo ne yapmaya çalıştığınızı otomatik olarak bilmiyor (ya da arkasındaki sebep nedir)?
NibblyPig

Automagic "Orada ne yapmaya çalıştığını görüyorum", genellikle sınıflar gibi şeyler bildirmeye gelince kaşlarını çatar, programcının türleri açıkça işaretlemesi daha iyidir. T döndüren yöntemlere sahip bir sınıfa "in" eklemeyi deneyebilirsiniz ve derleyici şikayet eder. Daha önce sizin için otomatik olarak eklediği "giriş" i sessizce kaldırırsa ne olacağını hayal edin.
Lasse V.Karlsen

1
Bir anahtar kelimenin bu uzun açıklamaya ihtiyacı varsa, açıkça yanlış olan bir şeyler vardır. Bence, C # bu özel durumda çok akıllı olmaya çalışıyor. Yine de, güzel açıklama için teşekkür ederim.
rr-

8

Bu gönderi , konuyla ilgili okuduğum en iyi yazı

Kısacası, kovaryans / kontravaryans / değişmezlik, otomatik tip dökümü ile ilgilidir (tabandan türetilene ve tersi). Bu tür yayınlar, yalnızca, dökülen nesneler üzerinde gerçekleştirilen okuma / yazma eylemleri açısından bazı garantilere uyulması halinde mümkündür. Daha fazla ayrıntı için yazıyı okuyun.


5
Bağlantı kesik görünüyor. İşte arşivlenmiş bir sürüm: web.archive.org/web/20140626123445/http://adamnathan.co.uk/…
si618

1
bu açıklamayı daha da çok seviyorum: codepureandsimple.com/…
volkit
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.