2013-10-20 14 views
6

Ich habe die folgende Post auf contra Varianz und Lasse V. Karlsen Antwort lautete:Was ist eine gemeinsame Programmierung der Kontra-Varianz?

Understanding Covariant and Contravariant interfaces in C#

Auch wenn ich das Konzept zu verstehen, ich verstehe nicht, warum es sinnvoll ist. Zum Beispiel, warum sollte jemand eine Nur-Lese-Liste (wie in der Post: List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>) machen.

Ich weiß auch, dass kontra Varianz Methoden Parameter überschreiben (konzeptionell sein, kann dies nicht in Gebrauch ist in C#, Java und C++, soweit ich weiß). Welche Beispiele gibt es, in denen dies sinnvoll ist?

Ich würde einige einfache reale Beispiele schätzen.

+1

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. –

+4

@DavidArno Aber im Kontext von C#, ohne Kontra- oder Kovarianz, gäbe es keinen Linq. –

+0

@MatthewWatson, wirklich? Nun, ich lebe und lerne! Ich wusste nicht, linq verwendet sie :) –

Antwort

2

(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.

1

Covariance ist nützlich für schreibgeschützte (out) Repositories; Kontravarianz für schreibgeschützte (in) Repositories.

public interface IReadRepository<out TVehicle> 
{ 
    TVehicle GetItem(Guid id); 
} 

public interface IWriteRepository<in TVehicle> 
{ 
    void AddItem(TVehicle vehicle); 
} 

Auf diese Weise ist eine Instanz IReadRepository<Car> auch eine Instanz von IReadRepository<Vehicle>, denn wenn man ein Auto aus dem Repository zu bekommen, ist es auch ein Fahrzeug ist; Eine Instanz von IWriteRepository<Vehicle> ist jedoch auch eine Instanz von IWriteRepository<Car>, da Sie, wenn Sie dem Repository ein Fahrzeug hinzufügen können, ein Auto in das Repository schreiben können.

Dies erklärt die Gründe hinter den Schlüsselwörtern out und in für Kovarianz und Kontravarianz.

Warum sollten Sie diese Trennung vornehmen (mit separaten schreibgeschützten und schreibgeschützten Schnittstellen, die höchstwahrscheinlich von derselben konkreten Klasse implementiert werden), so können Sie die Befehle sauber voneinander trennen (schreiben Operationen) und Abfragen (Leseoperationen), die wahrscheinlich unterschiedliche Anforderungen in Bezug auf Leistung, Konsistenz und Verfügbarkeit haben und daher unterschiedliche Strategien in Ihrer Codebasis benötigen.

0

Wenn Sie das Konzept der Kontravarianz nicht wirklich verstehen, können (und werden) solche Probleme auftreten.

Lassen Sie uns das einfachste Beispiel bauen:

public class Human 
{ 
    virtual public void DisplayLanguage() { Console.WriteLine("I do speak a language"); } 
} 
public class Asian : Human 
{ 
    override public void DisplayLanguage() { Console.WriteLine("I speak chinesse"); } 
} 

public class European : Human 
{ 
    override public void DisplayLanguage() { Console.WriteLine("I speak romanian"); } 
} 

Ok hier ist ein Beispiel für contra Varianz

public class test 
{ 
    static void ContraMethod(Human h) 
    { 
     h.DisplayLanguage(); 
    } 

    static void ContraForInterfaces(IEnumerable<Human> humans) 
    { 
     foreach (European euro in humans) 
     { 
      euro.DisplayLanguage(); 
     } 
    } 
    static void Main() 
    { 
     European euro = new European(); 
     test.ContraMethod(euro); 
     List<European> euroList = new List<European>() { new European(), new European(), new European() }; 

     test.ContraForInterfaces(euroList); 
    } 
} 

Vor .NET 4.0 die Methode ContraForInterfaces nicht erlaubt war (so dass sie fixiert tatsächlich um einen Fehler :)).

Ok, was bedeutet die ContraForInterfaces-Methode? Es ist einfach, sobald Sie eine Liste von Europäern gemacht haben, sind die Objekte in IMMER Europäer, auch wenn Sie sie an eine Methode übergeben, die IEnumerable <> übernimmt. Auf diese Weise werden Sie immer die richtige Methode für Ihr Objekt aufrufen. Covariance und ContraVariance ist nur Parameter Polymorphismus (und nichts mehr) galt jetzt auch für Delegaten und Schnittstellen. :)