(ich denke, diese Frage mehr über Kovarianz ist eher als Kontra, da das zitierte Beispiel ist mit Kovarianz zu tun.)
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
aus dem Zusammenhang gerissen, das ist ein wenig irreführend ist. In dem von Ihnen zitierten Beispiel wollte der Autor die Idee vermitteln, dass, wenn die List<Fish>
in Wirklichkeit auf eine es Fish
sein würde.
Aber das würde natürlich auch eine Cow
hinzufügen - was eindeutig falsch ist.
Der Compiler erlaubt daher nicht Sie eine Referenz auf eine List<Fish>
Referenz zuweisen.
Also wann wäre das tatsächlich sicher - und nützlich?
Es ist eine sichere Zuweisung, wenn die Sammlung nicht geändert werden kann. In C# kann eine IEnumerable<T>
eine nicht änderbare Sammlung darstellen.
So können Sie dies sicher tun:
IEnumerable<Animal> animals = GetAccessToFishes(); // for some reason, returns List<Animal>
, weil es keine Möglichkeit gibt, einen nicht-Fisch zu animals
hinzuzufügen. Es gibt keine Methoden, dies zu ermöglichen.
Also wann wäre das nützlich?
Dies ist nützlich, wenn Sie auf eine allgemeine Methode oder Eigenschaft einer Auflistung zugreifen möchten, die Elemente eines oder mehrerer Typen enthalten kann, die von einer Basisklasse abgeleitet sind.
Zum Beispiel könnten Sie eine Hierarchie haben, die verschiedene Kategorien von Lager für einen Supermarkt darstellt.
Nehmen wir an, dass die Basisklasse StockItem
eine Eigenschaft double SalePrice
hat.
Lassen Sie uns auch sagen, Sie haben eine Methode, Shopper.Basket()
, die eine IEnumerable<StockItem>
zurückgibt, die die Elemente darstellt, die ein Käufer in ihrem Korb hat. Die Gegenstände im Korb können von jeder konkreten Art sein, die von StockItem
abgeleitet ist.
In diesem Fall Sie die Preise aller Artikel im Warenkorb hinzufügen können (ich habe diese Langschrift geschrieben ohne Linq klar zu machen, mit was geschieht Real-Code IEnumerable.Sum()
natürlich verwenden würde.):
IEnumerable<StockItem> itemsInBasket = shopper.Basket;
double totalCost = 0.0;
foreach (var item in itemsInBasket)
totalCost += item.SalePrice;
Kontra
ein Beispiel für die Verwendung von Kontra ist, wenn Sie eine Aktion auf ein Element oder eine Sammlung von Artikeln über eine Basisklassentyp anwenden möchten, auch wenn Sie einen abgeleiteten Typ.
Zum Beispiel könnten Sie eine Methode haben, die eine Aktion jedes Element in einer Folge von StockItem
wie so angelegt:
void ApplyToStockItems(IEnumerable<StockItem> items, Action<StockItem> action)
{
foreach (var item in items)
action(item);
}
Mit dem StockItem
Beispiel nehmen wir an, es eine Print()
Methode hat, die Sie verwenden können, um es auf einen Kassenbon zu drucken. Man könnte dann wie folgt verwenden nennen:
Action<StockItem> printItem = item => { item.Print(); }
ApplyToStockItems(shopper.Basket, printItem);
In diesem Beispiel sind die Typen der Elemente in den Korb könnte Fruit
, Electronics
, Clothing
und so weiter sein. Aber weil sie alle von StockItem
stammen, funktioniert der Code mit allen von ihnen.
Hoffentlich ist der Nutzen dieser Art von Code klar! Dies ist sehr ähnlich zu der Art, wie viele Methoden in Linq funktionieren.
Möglicherweise entzündliche Bemerkung, aber man könnte argumentieren, dass sie nicht nützlich sind. F # zum Beispiel unterstützt ganz bewusst weder die Kovarianz noch die Kontravarianz. –
@DavidArno Aber im Kontext von C#, ohne Kontra- oder Kovarianz, gäbe es keinen Linq. –
@MatthewWatson, wirklich? Nun, ich lebe und lerne! Ich wusste nicht, linq verwendet sie :) –