Mit <out T>
können Sie die Schnittstellenreferenz als eine in der Hierarchie nach oben behandeln.
Mit <in T>
können Sie die Schnittstellenreferenz in der Hiearchy als eine nach unten behandeln.
Lassen Sie mich versuchen, es in mehr englischen Begriffen zu erklären.
Angenommen, Sie rufen eine Liste der Tiere aus Ihrem Zoo ab und beabsichtigen, sie zu verarbeiten. Alle Tiere (in Ihrem Zoo) haben einen Namen und eine eindeutige ID. Einige Tiere sind Säugetiere, einige sind Reptilien, einige sind Amphibien, einige sind Fische usw., aber sie sind alle Tiere.
Mit Ihrer Liste von Tieren (die Tiere verschiedener Arten enthält) können Sie also sagen, dass alle Tiere einen Namen haben, so dass es offensichtlich sicher wäre, den Namen aller Tiere zu erhalten.
Was ist jedoch, wenn Sie nur eine Liste von Fischen haben, diese aber wie Tiere behandeln müssen? Funktioniert das? Intuitiv sollte es funktionieren, aber in C # 3.0 und früher wird dieser Code nicht kompiliert:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
Der Grund dafür ist, dass der Compiler nicht "weiß", was Sie mit der Tiersammlung beabsichtigen oder tun können , nachdem Sie sie abgerufen haben. Nach allem, was es weiß, könnte es einen Weg geben IEnumerable<T>
, ein Objekt wieder in die Liste aufzunehmen, und dies würde es Ihnen möglicherweise ermöglichen, ein Tier, das kein Fisch ist, in eine Sammlung aufzunehmen, die nur Fisch enthalten soll.
Mit anderen Worten, der Compiler kann nicht garantieren, dass dies nicht zulässig ist:
animals.Add(new Mammal("Zebra"));
Der Compiler weigert sich also sofort, Ihren Code zu kompilieren. Das ist Kovarianz.
Schauen wir uns die Kontravarianz an.
Da unser Zoo mit allen Tieren umgehen kann, kann er mit Sicherheit auch mit Fischen umgehen. Versuchen wir also, unserem Zoo einige Fische hinzuzufügen.
In C # 3.0 und früheren Versionen wird dies nicht kompiliert:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));
Hier könnte der Compiler diesen Code zulassen, obwohl die Methode List<Animal>
einfach zurückgegeben wird, weil alle Fische Tiere sind. Wenn wir also nur die Typen dahingehend geändert haben:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Dann würde es funktionieren, aber der Compiler kann nicht feststellen, dass Sie dies nicht versuchen:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];
Da es sich bei der Liste tatsächlich um eine Liste von Tieren handelt, ist dies nicht zulässig.
Kontra- und Co-Varianz ist also, wie Sie Objektreferenzen behandeln und was Sie damit machen dürfen.
Die Schlüsselwörter in
und out
in C # 4.0 kennzeichnen die Schnittstelle speziell als die eine oder andere. Mit in
können Sie den generischen Typ (normalerweise T) in Eingabepositionen einfügen, dh Methodenargumente und Nur-Schreib-Eigenschaften.
Mit out
können Sie den generischen Typ in Ausgabepositionen platzieren, dh Methodenrückgabewerte, schreibgeschützte Eigenschaften und Out-Methodenparameter.
Auf diese Weise können Sie das tun, was mit dem Code beabsichtigt ist:
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>
hat sowohl In- als auch Out-Richtungen auf T, es ist also weder eine Co-Variante noch eine Contra-Variante, sondern eine Schnittstelle, mit der Sie Objekte wie das folgende hinzufügen können:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
würde Ihnen erlauben, dies zu tun:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
Hier sind einige Videos, die die Konzepte zeigen:
Hier ist ein Beispiel:
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;
}
}
}
Ohne diese Markierungen könnte Folgendes kompiliert werden:
public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
oder dieses:
public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
as Descendants