Z <out T>
można traktować jako jedno odniesienie interfejsu w górę w hierarchii.
Dzięki <in T>
można traktować odniesienie do interfejsu jako jedno w dół w hiearchacie.
Pozwólcie, że spróbuję wyjaśnić to w bardziej angielskich terminach.
Załóżmy, że pobierasz listę zwierząt ze swojego zoo i zamierzasz je przetworzyć. Wszystkie zwierzęta (w twoim zoo) mają imię i unikalny identyfikator. Niektóre zwierzęta to ssaki, niektóre to gady, niektóre to płazy, niektóre to ryby itp., Ale wszystkie są zwierzętami.
Tak więc, ze swoją listą zwierząt (która zawiera zwierzęta różnych typów), można powiedzieć, że wszystkie zwierzęta mają nazwę, więc oczywiście można bezpiecznie uzyskać nazwę wszystkich zwierząt.
Co jednak, jeśli masz tylko listę ryb, ale musisz traktować je jak zwierzęta, czy to działa? Intuicyjnie, to powinno działać, ale w C# 3.0 i wcześniej, ten fragment kodu nie kompilacji:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
Powodem tego jest to, że kompilator nie „wie”, co zamierzasz, lub może , zrób kolekcję zwierząt po jej odzyskaniu. Z tego, co wie, może istnieć sposób, aby IEnumerable<T>
umieścić obiekt z powrotem na liście, a to potencjalnie pozwoliłoby na umieszczenie zwierzęcia, które nie jest rybą, w kolekcji, która powinna zawierać tylko ryby.
Innymi słowy, kompilator nie może zagwarantować, że nie jest to dozwolone:
animals.Add(new Mammal("Zebra"));
więc kompilator po prostu wręcz odmawia skompilować kod. To jest kowariancja.
Spójrzmy na sprzeczność.
Ponieważ nasze zoo może obsłużyć wszystkie zwierzęta, z pewnością poradzi sobie z rybami, więc spróbujmy dodać trochę ryb do naszego zoo.
W języku C# 3.0 i wcześniej, to nie skompilować:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));
Tutaj kompilator mógłby pozwalają ten kawałek kodu, choć metoda zwraca List<Animal>
po prostu dlatego, że wszystkie ryby są zwierzętami, więc jeśli tylko zmienił typy do to:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Wtedy to działa, ale kompilator nie może określić, że nie próbujesz to zrobić:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];
Ponieważ lista jest w rzeczywistości listą zwierząt, nie jest to dozwolone.
Tak więc przeciwieństwo i współzmienność to sposób traktowania odniesień do obiektów i tego, co wolno z nimi robić.
Słowa kluczowe w C# 4.0 wyraźnie oznaczają interfejs jako jeden lub drugi. Z in
, możesz umieścić typ ogólny (zwykle T) w wejściu -pozycje, co oznacza argumenty metody i właściwości tylko do zapisu.
Z out
, masz prawo do umieszczenia typu rodzajowego w wyjście -positions, czyli wartości Sposób powrotu, tylko do odczytu właściwości i parametry się metoda.
To pozwoli Ci zrobić to, co zamierza zrobić z kodem:
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>
ma zarówno wewnątrz i na zewnątrz na T-kierunkach, więc nie jest to ani CO ani contra wariant wariant, ale interfejs, który pozwolił na dodawanie obiektów, takich jak to:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
pozwoli Ci to zrobić:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
Oto kilka filmów, który pokazuje pojęcia:
Oto przykład:
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;
}
}
}
Bez tych znaków, następujące mógł skompilować:
public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
lub to:
public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
as Descendants
Może być przydatny: [Blog Post] (http://weblogs.asp.net/paulomorgado/archive/2010/04/13/c-4-0-covariance-and-contravariance-in-generics.aspx) – Krunal
To jest krótkie i dobre wydarzenie IMHO: http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx – digEmAll
Hmm jest dobre, ale nie wyjaśnij _why_, co tak naprawdę mnie niepokoi. – NibblyPig