2010-04-27 6 views
69

Ich habe diese in einem Lehrbuch über C# gelesen, aber ich habe Schwierigkeiten, sie zu verstehen, wahrscheinlich aufgrund des Mangels an Kontext.Kovariante und kontravariante Schnittstellen verstehen in C#

Gibt es eine gute und prägnante Erklärung dafür, was sie sind und wofür sie nützlich sind?

Bearbeiten zur Klarstellung:

Covariant Schnittstelle:

interface IBibble<out T> 
. 
. 

kontra-Schnittstelle:

interface IBibble<in T> 
. 
. 
+0

Kann nützlich sein: [Blogbeitrag] (http://weblogs.asp.net/paulomorgado/archive/2010/04/13/c-4-0-covariance-and-contravariance-in-generics.aspx) – Krunal

+2

Dies ist eine kurze und gute expalnation IMHO: http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx – digEmAll

+0

Hmm, es ist gut, aber es nicht erkläre warum, was mich wirklich verwirrt. – NibblyPig

Antwort

113

Mit <out T>, können Sie die Interface-Referenz als eine nach oben in der Hierarchie behandeln.

Mit <in T> können Sie die Schnittstellenreferenz als eine abwärts in der Hierarchie behandeln.

Lassen Sie mich versuchen, es in mehr englischen Begriffen zu erklären.

Angenommen, Sie rufen eine Liste mit Tieren aus Ihrem Zoo ab, und Sie 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.

Also, mit Ihrer Liste von Tieren (die Tiere verschiedener Arten enthält), können Sie sagen, dass alle Tiere einen Namen haben, so dass es offensichtlich sicher wäre, den Namen aller Tiere zu erhalten.

Aber was ist, wenn Sie nur eine Liste von Fischen haben, aber sie wie Tiere behandeln müssen, funktioniert das? Intuitiv sollte es funktionieren, aber in C# 3.0 und vor, dieses Stück Code wird nicht kompiliert:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish> 

Der Grund dafür ist, dass der Compiler nicht „wissen“, was Sie beabsichtigen, oder kann , machen Sie mit der Tiersammlung, nachdem Sie es abgerufen haben. Nach allem, was es weiß, könnte es einen Weg durch IEnumerable<T> geben, um ein Objekt zurück in die Liste zu setzen, und das würde es möglicherweise erlauben, ein Tier, das kein Fisch ist, in eine Sammlung zu legen, die nur Fische enthalten soll.

Mit anderen Worten, kann der Compiler nicht garantieren, dass dies nicht erlaubt ist:

animals.Add(new Mammal("Zebra")); 

So ist der Compiler nur weigert sich geradezu um Ihren Code zu kompilieren. Das ist Kovarianz.

Schauen wir uns die Kontravarianz an.

Da unser Zoo mit allen Tieren umgehen kann, kann er sicherlich mit Fischen umgehen, also versuchen wir, unserem Zoo etwas Fisch hinzuzufügen.

In C# 3.0 und bevor dies nicht kompiliert:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal> 
fishes.Add(new Fish("Guppy")); 

Hier wird der Compiler dieses Stück Code ermöglichen könnte, auch wenn die Methode gibt List<Animal> einfach, da alle Fische sind Tiere, also wenn wir verändern nur die Typen dies:

List<Animal> fishes = GetAccessToFishes(); 
fishes.Add(new Fish("Guppy")); 

Dann würde es funktionieren, aber der Compiler kann nicht feststellen, dass Sie versuchen, dies nicht zu tun:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal> 
Fish firstFist = fishes[0]; 

Da die Liste eigentlich eine Liste von Tieren ist, ist dies nicht erlaubt.

Also Kontra-und Kovarianz ist, wie Sie Objektreferenzen behandeln und was Sie damit tun dürfen.

Die Schlüsselwörter in und out in C# 4.0 markiert speziell die Schnittstelle als das eine oder das andere. Mit in können Sie den generischen Typ (normalerweise T) in Eingabe -positions platzieren, was Methodenargumente und schreibgeschützte Eigenschaften bedeutet.

Mit out, sind Sie den generischen Typen in Ausgabe -Positionen zu platzieren erlaubt, welche Methode Rückgabewert sind, schreibgeschützte Eigenschaften und aus Verfahrensparameter.

Dies ermöglicht es Ihnen zu tun, was mit dem Code zu tun gedachten: hat

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> sowohl auf T In- und Out-Richtungen, so dass es weder Co-Variante noch kontravarianten, aber eine Schnittstelle, die Sie Objekte hinzufügen, wie folgt aus:

interface IWriteOnlyList<in T> 
{ 
    void Add(T value); 
} 

Ihnen erlauben würde, dies zu tun:

IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns 
                  IWriteOnlyList<Animal> 
fishes.Add(new Fish("Guppy")); <-- this is now safe 

Hier ein paar Videos, die die Konzepte zeigt:

Hier 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 Marken könnte die folgenden kompilieren:

public List<Descendant> GetDescendants() ... 
List<Base> bases = GetDescendants(); 
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant 

oder dies:

public List<Base> GetBases() ... 
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases 
               as Descendants 
+0

Hmm, könnten Sie das Ziel von Kovarianz und Kontravarianz erklären? Es könnte mir helfen, es mehr zu verstehen. – NibblyPig

+1

Sehen Sie das letzte Bit, was der Compiler vorher verhindert hat, der Zweck von in und out ist zu sagen, was Sie mit den Schnittstellen (oder Typen) tun können, die sicher sind, so dass der Compiler Sie nicht daran hindert, Sicherheit zu schaffen Dinge. –

+0

Hervorragende Antwort, ich sah die Videos, die sie sehr hilfreich waren, und kombiniert mit Ihrem Beispiel habe ich es jetzt sortiert. Nur eine Frage bleibt, und deshalb sind 'out' und 'in' erforderlich, warum erkennt Visual Studio nicht automatisch, was Sie zu tun versuchen (oder was ist der Grund dafür)? – NibblyPig

4

This post ist das Beste, was ich zu diesem Thema gelesen habe

Kurz gesagt, Kovarianz/Kontra/Invarianz befasst sich mit automatischen Typ Casting (von Basis zu abgeleiteten und umgekehrt).Diese Typumwandlungen sind nur möglich, wenn einige Garantien in Bezug auf Lese-/Schreibaktionen an den gegossenen Objekten eingehalten werden. Lesen Sie den Beitrag für weitere Details.

+0

sehr guter Artikel in der Tat. :) danke – gsimoes

+4

Link scheint tot. Hier ist eine archivierte Version: https://web.archive.org/web/20140626123445/http://adamnathan.co?/?p=75 – si618