Kovaryans ve karşıtlık gerçek dünya örneği


162

Gerçek dünyada kovaryans ve karşıtlığı nasıl kullanacağımı anlamakta biraz sorun yaşıyorum.

Şimdiye kadar gördüğüm tek örnek aynı eski dizi örneği oldu.

object[] objectArray = new string[] { "string 1", "string 2" };

Başka bir yerde kullanıldığını görebilseydim, gelişimim sırasında kullanmama izin verecek bir örnek görmek güzel olurdu.


1
Bu soruyu (kendi) şu cevaba kovaryans araştırıyorum : kovaryans türleri: örnek olarak . Bence ilginç ve umarım öğretici bulacaksınız.
Cristian Diaconescu

Yanıtlar:


109

Diyelim ki bir Sınıf Kişiniz ve bundan türeyen bir sınıfınız var, Öğretmen. IEnumerable<Person>Bağımsız değişken olarak alan bazı işlemleriniz var . Okul sınıfınızda bir döndüren yönteminiz var IEnumerable<Teacher>. Kovaryans, IEnumerable<Person>daha az türetilmiş (daha genel) bir tür yerine daha türetilmiş bir türün yerini alan bir yöntemi doğrudan kullanmanıza izin verir . Zıtlık, sezgisel olarak, daha türetilmiş bir türün belirtildiği daha genel bir tür kullanmanıza izin verir.

Ayrıca bkz . MSDN'de Generics'te Kovaryans ve Çelişki .

Sınıflar :

public class Person 
{
     public string Name { get; set; }
} 

public class Teacher : Person { } 

public class MailingList
{
    public void Add(IEnumerable<out Person> people) { ... }
}

public class School
{
    public IEnumerable<Teacher> GetTeachers() { ... }
}

public class PersonNameComparer : IComparer<Person>
{
    public int Compare(Person a, Person b) 
    { 
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : Compare(a,b);
    }

    private int Compare(string a, string b)
    {
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : a.CompareTo(b);
    }
}

Kullanımı :

var teachers = school.GetTeachers();
var mailingList = new MailingList();

// Add() is covariant, we can use a more derived type
mailingList.Add(teachers);

// the Set<T> constructor uses a contravariant interface, IComparer<in T>,
// we can use a more generic type than required.
// See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax
var teacherSet = new SortedSet<Teachers>(teachers, new PersonNameComparer());

14
@FilipBartuzi - eğer bu yanıtı yazdığımda benim gibi, gerçek bir dünya örneği olan bir üniversitede çalışmış olsaydınız.
tvanfosson

5
Bu, soruyu cevaplamadığında ve c # 'da co / contra variance kullanımına ilişkin herhangi bir örnek vermediğinde cevabı nasıl işaretleyebilir?
barakcaf

@barakcaf bir çelişki örneği ekledi. neden kovaryans örneğini görmediğinizden emin değilim - belki de kodu aşağı kaydırmanız gerekiyordu - ama bunun üzerine bazı yorumlar ekledim.
tvanfosson

@ tvanfosson kod co / contra kullanır, nasıl bildirileceğini göstermez. Örnek, diğer yanıtta olduğu gibi genel bildirimde giriş / çıkış kullanımını göstermez.
barakcaf

Yani, eğer doğru yaparsam, Liskov'un C #'daki ikame ilkesine izin veren kovaryans, doğru mu?
Miguel Veloso

136
// Contravariance
interface IGobbler<in T> {
    void gobble(T t);
}

// Since a QuadrupedGobbler can gobble any four-footed
// creature, it is OK to treat it as a donkey gobbler.
IGobbler<Donkey> dg = new QuadrupedGobbler();
dg.gobble(MyDonkey());

// Covariance
interface ISpewer<out T> {
    T spew();
}

// A MouseSpewer obviously spews rodents (all mice are
// rodents), so we can treat it as a rodent spewer.
ISpewer<Rodent> rs = new MouseSpewer();
Rodent r = rs.spew();

Tamamlamak için…

// Invariance
interface IHat<T> {
    void hide(T t);
    T pull();
}

// A RabbitHat…
IHat<Rabbit> rHat = RabbitHat();

// …cannot be treated covariantly as a mammal hat…
IHat<Mammal> mHat = rHat;      // Compiler error
// …because…
mHat.hide(new Dolphin());      // Hide a dolphin in a rabbit hat??

// It also cannot be treated contravariantly as a cottontail hat…
IHat<CottonTail> cHat = rHat;  // Compiler error
// …because…
rHat.hide(new MarshRabbit());
cHat.pull();                   // Pull a marsh rabbit out of a cottontail hat??

138
Bu gerçekçi örneği seviyorum. Ben sadece geçen hafta bazı eşek gobbling kodu yazıyordum ve şimdi kovaryans var sevindim. :-)
Eric Lippert

4
@Javadba ile bu yorumda, EricLippert'e kovaryans ve kontravaryansın ne olduğunu söyleyen, büyükanneme nasıl yumurta emeceğini söyleyen gerçekçi bir kovaryant örnek! : p
iAteABug_And_iLiked_it

1
Soru, karşıtlık ve kovaryansın ne yapabileceğini sormadı, neden kullanmanız gerektiğini sordu . Örneğiniz pratik olmaktan uzaktır, çünkü her ikisini de gerektirmez. Bir QuadrupedGobbler oluşturabilir ve kendim gibi davranabilirim (IGobbler <Quadruped> 'a atayın) ve yine Eşekleri silip süpürebilir (Bir Eşeği Dörtlü gerektiren Gobble yöntemine geçirebilirim). Karşıtlık gerektirmez. Bu s ki bu serin olabilir bir DonkeyGobbler gibi bir QuadrupedGobbler tedavi, ancak bir QuadrupedGobbler zaten eşekler yalayıp yutmak eğer neden biz, bu durumda, gerekir?
wired_in

1
@wired_in Çünkü sadece eşekleri önemsediğinizde daha genel olmak engel olabilir. Örneğin, gobbled olacak eşekler sağlayan bir çiftliğiniz varsa, bunu şu şekilde ifade edebilirsiniz void feed(IGobbler<Donkey> dg). Bunun yerine bir parametre olarak bir IGobbler <Quadruped> aldıysanız, yalnızca eşek yiyen bir ejderhaya geçemezdiniz.
Marcelo Cantos

1
Waaay partiye geç, ama bu SO etrafında gördüğüm en iyi yazılı örnek hakkında. Saçma olurken tam mantıklı.
Oyunumu

122

Farkı anlamama yardımcı olmak için bir araya getirdiklerim

public interface ICovariant<out T> { }
public interface IContravariant<in T> { }

public class Covariant<T> : ICovariant<T> { }
public class Contravariant<T> : IContravariant<T> { }

public class Fruit { }
public class Apple : Fruit { }

public class TheInsAndOuts
{
    public void Covariance()
    {
        ICovariant<Fruit> fruit = new Covariant<Fruit>();
        ICovariant<Apple> apple = new Covariant<Apple>();

        Covariant(fruit);
        Covariant(apple); //apple is being upcasted to fruit, without the out keyword this will not compile
    }

    public void Contravariance()
    {
        IContravariant<Fruit> fruit = new Contravariant<Fruit>();
        IContravariant<Apple> apple = new Contravariant<Apple>();

        Contravariant(fruit); //fruit is being downcasted to apple, without the in keyword this will not compile
        Contravariant(apple);
    }

    public void Covariant(ICovariant<Fruit> fruit) { }

    public void Contravariant(IContravariant<Apple> apple) { }
}

TLDR

ICovariant<Fruit> apple = new Covariant<Apple>(); //because it's covariant
IContravariant<Apple> fruit = new Contravariant<Fruit>(); //because it's contravariant

10
Bu şimdiye kadar gördüğüm en iyi şey, açık ve özlü.
Rob L

6
Meyvesi Contravariancene zaman Fruitebeveyni ( örnekte) elmaya nasıl düşürülebilir Apple?
Tobias Marschall

@TobiasMarschall Bu "polimorfizm" üzerinde daha fazla çalışmak zorunda anlamına gelir
snr

56

Giriş ve çıkış anahtar sözcükleri, derleyicinin genel parametreleri olan arabirimler ve delegeler için yayınlama kurallarını denetler:

interface IInvariant<T> {
    // This interface can not be implicitly cast AT ALL
    // Used for non-readonly collections
    IList<T> GetList { get; }
    // Used when T is used as both argument *and* return type
    T Method(T argument);
}//interface

interface ICovariant<out T> {
    // This interface can be implicitly cast to LESS DERIVED (upcasting)
    // Used for readonly collections
    IEnumerable<T> GetList { get; }
    // Used when T is used as return type
    T Method();
}//interface

interface IContravariant<in T> {
    // This interface can be implicitly cast to MORE DERIVED (downcasting)
    // Usually means T is used as argument
    void Method(T argument);
}//interface

class Casting {

    IInvariant<Animal> invariantAnimal;
    ICovariant<Animal> covariantAnimal;
    IContravariant<Animal> contravariantAnimal;

    IInvariant<Fish> invariantFish;
    ICovariant<Fish> covariantFish;
    IContravariant<Fish> contravariantFish;

    public void Go() {

        // NOT ALLOWED invariants do *not* allow implicit casting:
        invariantAnimal = invariantFish; 
        invariantFish = invariantAnimal; // NOT ALLOWED

        // ALLOWED covariants *allow* implicit upcasting:
        covariantAnimal = covariantFish; 
        // NOT ALLOWED covariants do *not* allow implicit downcasting:
        covariantFish = covariantAnimal; 

        // NOT ALLOWED contravariants do *not* allow implicit upcasting:
        contravariantAnimal = contravariantFish; 
        // ALLOWED contravariants *allow* implicit downcasting
        contravariantFish = contravariantAnimal; 

    }//method

}//class

// .NET Framework Examples:
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { }
public interface IEnumerable<out T> : IEnumerable { }


class Delegates {

    // When T is used as both "in" (argument) and "out" (return value)
    delegate T Invariant<T>(T argument);

    // When T is used as "out" (return value) only
    delegate T Covariant<out T>();

    // When T is used as "in" (argument) only
    delegate void Contravariant<in T>(T argument);

    // Confusing
    delegate T CovariantBoth<out T>(T argument);

    // Confusing
    delegate T ContravariantBoth<in T>(T argument);

    // From .NET Framework:
    public delegate void Action<in T>(T obj);
    public delegate TResult Func<in T, out TResult>(T arg);

}//class

Balık farz edilmek, Hayvan'ın bir alt türüdür. Bu arada harika bir cevap.
Rajan Prasad

48

İşte bir miras hiyerarşisi kullanan basit bir örnek.

Basit sınıf hiyerarşisi göz önüne alındığında:

resim açıklamasını buraya girin

Ve kodda:

public abstract class LifeForm  { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }

Değişmezlik (yani, genel tip parametreleri * ile süslenmemiş *in veya outanahtar kelimeler)

Görünüşe göre, böyle bir yöntem

public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
    foreach (var lifeForm in lifeForms)
    {
        Console.WriteLine(lifeForm.GetType().ToString());
    }
}

... heterojen bir koleksiyonu kabul etmelidir:

var myAnimals = new List<LifeForm>
{
    new Giraffe(),
    new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra

Ancak, daha türetilmiş tipte bir koleksiyonu geçmek başarısız olur!

var myGiraffes = new List<Giraffe>
{
    new Giraffe(), // "Jerry"
    new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // Compile Error!

cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'

Neden? Genel parametre IList<LifeForm>kovaryant olmadığından - IList<T>değişmez olduğundan, IList<LifeForm>yalnızca parametrelenen türün Tolması gereken koleksiyonları (IList uygulayan) kabul ederLifeForm .

Yöntemin uygulanması PrintLifeFormskötü amaçlıysa (ancak aynı yöntem imzasına sahipse), derleyicinin geçişini engellemesinin nedeni List<Giraffe>açıktır:

 public static void PrintLifeForms(IList<LifeForm> lifeForms)
 {
     lifeForms.Add(new Zebra());
 }

Yana IListizin ekleme ya da elemanların çıkarılması, herhangi bir alt-sınıfı LifeForm, böylece bir parametre ilave edilebilir lifeForms, ve elde edilen tiplerinin herhangi toplama tipi ihlal edeceği yönteme geçirildi. (Burada, kötü niyetli yöntem eklemek girişiminde ZebraTo var myGiraffes). Neyse ki, derleyici bizi bu tehlikeden korur.

Kovaryans (Parametrik tip süslemeli genel out)

Kovaryans, değişmez koleksiyonlarla yaygın olarak kullanılır (yani bir koleksiyona yeni elemanların eklenemediği veya koleksiyondan çıkarılamadığı durumlarda)

Yukarıdaki örneğin çözümü, bir kovaryant jenerik toplama tipinin kullanılmasını sağlamaktır, örn. IEnumerable(Olarak tanımlanmıştır IEnumerable<out T>). IEnumerablekoleksiyona geçmek için herhangi bir yöntem yoktur ve outkovaryansın bir sonucu olarak, alt tipine sahip herhangi bir koleksiyon LifeFormartık yönteme geçirilebilir:

public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms)
{
    foreach (var lifeForm in lifeForms)
    {
        Console.WriteLine(lifeForm.GetType().ToString());
    }
}

PrintLifeFormsŞimdi ile çağrılabilir Zebras, Giraffesve herhangi IEnumerable<>herhangi alt sınıfınınLifeForm

Çelişki (Parametrik tip süslemeli genel in)

İşlevler parametre olarak iletildiğinde karşıtlık sıklıkla kullanılır.

Aşağıda Action<Zebra>parametre olarak alan ve bilinen bir Zebra örneğinde çağıran bir işlev örneği verilmiştir:

public void PerformZebraAction(Action<Zebra> zebraAction)
{
    var zebra = new Zebra();
    zebraAction(zebra);
}

Beklendiği gibi, bu iyi çalışıyor:

var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra"));
PerformZebraAction(myAction); // I'm a zebra

Sezgisel olarak, bu başarısız olacaktır:

var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe"));
PerformZebraAction(myAction); 

cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'

Ancak, bu başarılı

var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal"));
PerformZebraAction(myAction); // I'm an animal

ve bu da başarılı olur:

var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba"));
PerformZebraAction(myAction); // I'm an amoeba

Neden? Çünkü Actionolarak tanımlanır Action<in T>, yani bunun contravariantiçin, yani, Action<Zebra> myActionbu, myAction"en", bir de olabilir Action<Zebra>ki, ama daha az türetilmiş süper sınıf Zebrada kabul edilebilir.

Bu başlangıçta sezgisel olmasa da (örneğin Action<object>, bir parametre gerektiren nasıl geçirilebilir Action<Zebra>?), Adımları kaldırırsanız, çağrılan fonksiyonun ( PerformZebraAction) kendisinin veri geçirmekten sorumlu olduğunu (bu örnekte bir Zebraörnek ) göreceksiniz. ) işleve - veriler arama kodundan gelmez.

Daha yüksek dereceli işlevleri bu şekilde kullanma tersine çevrilmiş yaklaşımı nedeniyle Action, çağrıldığında, işlevin kendisi daha az türetilmiş bir tür kullanmasına rağmen, işleve (parametre olarak iletilir) Zebraçağrılan daha türetilmiş örnektir zebraAction.


7
Bu, farklı varyans seçenekleri için harika bir açıklamadır, çünkü örnek üzerinden konuşur ve derleyicinin
içeri

Kontravaryansin için anahtar kelime nerede kullanılır ?
javadba

Yukarıda @javadba, Action<in T>ve Func<in T, out TResult>giriş türü tersine de bulunmaktadır. (Örneklerim mevcut değişmez (Liste), kovaryant (IEnumerable) ve kontravaryant (Action, Func) türlerini kullanıyor)
StuartLC

Tamam bunu yapmam bunu C#bilmezdim.
javadba

Scala'da oldukça benzer, sadece farklı sözdizimi - [+ T] T'de kovaryant olur, [-T] T'de çelişkili olur, Scala da 'arasındaki' kısıtlamayı ve 'Hiçbir şey' karışık alt sınıfını uygulayabilir. yok.
StuartLC

32
class A {}
class B : A {}

public void SomeFunction()
{
    var someListOfB = new List<B>();
    someListOfB.Add(new B());
    someListOfB.Add(new B());
    someListOfB.Add(new B());
    SomeFunctionThatTakesA(someListOfB);
}

public void SomeFunctionThatTakesA(IEnumerable<A> input)
{
    // Before C# 4, you couldn't pass in List<B>:
    // cannot convert from
    // 'System.Collections.Generic.List<ConsoleApplication1.B>' to
    // 'System.Collections.Generic.IEnumerable<ConsoleApplication1.A>'
}

Temelde bir Numaralı türünü alan bir işleve sahip olduğunuzda, açıkça türetmeden Türetilmiş türetilmiş bir numaraya geçemezsiniz.

Sizi bir tuzak hakkında uyarmak için:

var ListOfB = new List<B>();
if(ListOfB is IEnumerable<A>)
{
    // In C# 4, this branch will
    // execute...
    Console.Write("It is A");
}
else if (ListOfB is IEnumerable<B>)
{
    // ...but in C# 3 and earlier,
    // this one will execute instead.
    Console.Write("It is B");
}

Bu yine de korkunç bir kod, ancak var ve C # 4'te değişen davranış, böyle bir yapı kullanırsanız, ince ve zor bulmak hataları getirebilir.


Bu, koleksiyonları her şeyden daha fazla etkiler, çünkü c # 3'te daha türetilmiş bir türü, daha az türetilmiş türdeki bir yönteme geçirebilirsiniz.
Razor

3
Evet, büyük değişiklik IEnumerable'un bunu daha önce desteklemesi, oysa daha önce desteklememesidir.
Michael Stum

4

Gönderen MSDN

Aşağıdaki kod örneği, yöntem grupları için kovaryans ve karşıtlık desteğini gösterir

static object GetObject() { return null; }
static void SetObject(object obj) { }

static string GetString() { return ""; }
static void SetString(string str) { }

static void Test()
{
    // Covariance. A delegate specifies a return type as object, 
    // but you can assign a method that returns a string.
    Func<object> del = GetString;

    // Contravariance. A delegate specifies a parameter type as string, 
    // but you can assign a method that takes an object.
    Action<string> del2 = SetObject;
}

4

contravariance

Gerçek dünyada, hayvanlar için her zaman tavşan barınağı yerine bir barınak kullanabilirsiniz, çünkü bir hayvan barınağı her tavşana ev sahipliği yaptığında bir hayvandır. Ancak, bir hayvan barınağı yerine bir tavşan barınağı kullanırsanız, personeli bir kaplan tarafından yenebilir.

Kodda, bu, eğer varsa , sadece böyle yöntem parametreleri olarak söz veriyorsanız ve kullanmanız durumundaIShelter<Animal> animals yazabileceğiniz anlamına gelir :IShelter<Rabbit> rabbits = animals TIShelter<T>

public class Contravariance
{
    public class Animal { }
    public class Rabbit : Animal { }

    public interface IShelter<in T>
    {
        void Host(T thing);
    }

    public void NoCompileErrors()
    {
        IShelter<Animal> animals = null;
        IShelter<Rabbit> rabbits = null;

        rabbits = animals;
    }
}

ve bir öğeyi daha genel bir öğeyle değiştirin, yani varyansı azaltın veya kontra varyans ekleyin.

Kovaryans

Gerçek dünyada, bir hayvan tedarikçisi yerine her zaman bir tavşan tedarikçisi kullanabilirsiniz, çünkü bir tavşan tedarikçisi size her tavşan verdiğinde bir hayvandır. Bununla birlikte, bir tavşan tedarikçisi yerine bir hayvan tedarikçisi kullanıyorsanız, bir kaplan tarafından yenilebilir.

Kodda, bu, eğer varsa , sadece yöntem döndürme değerleri olarak söz veriyorsanız ve kullanırsanız ISupply<Rabbit> rabbitsyazabileceğiniz anlamına gelir :ISupply<Animal> animals = rabbits TISupply<T>

public class Covariance
{
    public class Animal { }
    public class Rabbit : Animal { }

    public interface ISupply<out T>
    {
        T Get();
    }

    public void NoCompileErrors()
    {
        ISupply<Animal> animals = null;
        ISupply<Rabbit> rabbits = null;

        animals = rabbits;
    }
}

ve bir maddeyi daha türetilmiş bir öğeyle değiştirin, yani varyansı artırın veya birlikte varyans ekleyin.

Sonuçta, bu sadece derleme zamanı kontrol edilebilir sizden tür güvenliğini sağlamak ve kimseyi yenmemek için jenerik bir türe belirli bir şekilde davranacağınız sözdür.

Başınızı bunun etrafına iki kez sarmak için buna bir okuma vermek isteyebilirsiniz .


Bir kaplan tarafından yemiş olabilir Bu bir değer oldu
javadba

Hakkındaki yorumunuz contravarianceilginç. Ben operasyonel bir gereklilik olarak okuyorum : daha genel tip ondan türetilen her türlü kullanım durumlarını desteklemesi gerektiğini. Dolayısıyla bu durumda hayvan barınağı , her hayvan türünün korunmasını destekleyebilmelidir. Bu durumda yeni bir alt sınıf eklemek süper sınıfı bozabilir! Yani - Tyrannosaurus Rex'in bir alt tipini eklersek , mevcut hayvan barınağımızı mahvedebilir .
javadba

(Devam etti). Bu , yapısal olarak açıkça tarif edilen kovaryanstan keskin bir şekilde farklıdır : daha spesifik alt türler, süper tipte tanımlanan işlemleri destekler - ancak aynı şekilde olması gerekmez.
javadba

3

Dönüştürücü delegesi, her iki kavramı da birlikte çalışmama yardımcı oluyor:

delegate TOutput Converter<in TInput, out TOutput>(TInput input);

TOutputtemsil eder kovaryans yöntemi, bir döner daha özel türü .

TInputbir yöntemin daha az spesifik bir tipten geçirildiği karşıtlığı temsil eder .

public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }

public static Poodle ConvertDogToPoodle(Dog dog)
{
    return new Poodle() { Name = dog.Name };
}

List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();
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.