2010-10-27 17 views
18

Ist es möglich, eine List<Subclass> zu List<Superclass> in C# 4.0 zu werfen?Kovarianz in C#

Etwas in dieser Richtung:

class joe : human {} 

List<joe> joes = GetJoes(); 

List<human> humanJoes = joes; 

Ist das nicht das, was Kovarianz für ist?

wenn Sie tun können:

human h = joe1 as human; 

warum sollten Sie nicht in der Lage sein

List<human> humans = joes as List<human>; 

zu tun, als es nicht legal wäre, zu tun (joe) Menschen [0], weil das Gegenstand wurde niedergeschlagen .. und jeder würde glücklich sein. Die einzige Alternative besteht darin, eine neue Liste zu erstellen.

+2

Dies ist im Grunde das gleiche wie [In C#, warum kann nicht ein Liste Objekt in einer Variablen Liste gespeichert werden] (http://stackoverflow.com/questions/6557/in-c-why-cant-a-liststring-Objekt, das in einer Listenobjektvariablen gespeichert werden soll. –

+0

, weil "Menschen" sich dann auf eine Instanz von "Liste " beziehen würde, was zu Problemen führen würde, wie in @Jons Beispiel dargestellt. –

+0

yep, nachdem er das Beispiel korrigiert habe ich habe es .. macht Sinn –

Antwort

25

Sie können dies nicht tun, weil es nicht sicher wäre. Bedenken Sie:

List<Joe> joes = GetJoes();  
List<Human> humanJoes = joes; 
humanJoes.Clear(); 
humanJoes.Add(new Fred()); 
Joe joe = joes[0]; 

eindeutig die letzte Zeile (wenn nicht eine frühere) hat zum Scheitern verurteilt - als Fred kein Joe ist. Die Invarianz von List<T> verhindert diesen Fehler bei kompilieren Zeit statt Ausführungszeit.

+0

gut humanJoes eine Liste von Human ist, also sollte man nicht versuchen (oder kann) extra einen Joe daraus machen. aber jeder Joe ist ein Mensch, also sollte es sicher sein, alle in die Unterklasse sicher zu werfen. –

+0

Sie können sicherlich Human h = joesList [0] als Mensch .. und das ist, was ich suchte/Fragen über ... auf einer ganzen Liste Ebene –

+0

akzeptieren @Sonic: Warum sollten Sie nicht ein zusätzliches 'Joe' hinzufügen oder 'Fred' dazu? * Diese Art der Abweichung ist * immer * akzeptabel. Probieren Sie es aus: 'List list = new Liste (); list.Add ("Dies ist ein String)"; '. Warum sollten Sie erwarten, dass das scheitert? –

2

Nein. Die Co/Contravarianz-Funktionen von C# 4.0 unterstützen nur Schnittstellen und Delegaten. Betonarten wie List<T> werden nicht unterstützt.

+0

http://stackoverflow.com/questions/245607/how-isgeneric-covariance-contra-varianz-implementiert-in-cc-4-0 –

+0

also müsste ich ein neues Objekt für jeden Joe als Mensch durchlaufen und ein neues Objekt erstellen und zu einer neuen Liste hinzufügen? das ist die einzige option? –

+1

Es ist nicht möglich mit 'IList ', weil das invariant ist. –

6

instanziiert eine neue Mensch-Liste, die die joes als Eingabe:

List<human> humanJoes = new List<human>(joes); 
1

Nein. Wie Jared sagte, unterstützen die Co/Contravarianz-Funktionen von C# 4.0 nur Schnittstellen und Delegaten. Es funktioniert jedoch auch nicht mit IList<T>, und der Grund ist, dass IList<T> Methoden zum Hinzufügen und Ändern von Elementen in der Liste enthält - wie Jon Skeet neue Antwort sagt.

Der einzige Weg, um eine Liste von „Joe“ zu „human“ zu können, ist werfen, wenn die Schnittstelle rein schreibgeschützt ist durch Design, etwa wie folgt:

public interface IListReader<out T> : IEnumerable<T> 
{ 
    T this[int index] { get; } 
    int Count { get; } 
} 

Selbst eine Contains(T item) Methode würde nicht erlaubt sein, denn wenn Sie IListReader<joe> zu IListReader<human> umwandeln, gibt es keine Contains(human item) Methode in IListReader<joe>.

Sie könnten „zwingen“ eine Besetzung von IList<joe> zu IListReader<joe>, IListReader<human> oder sogar IList<human> mit einem GoInterface. Aber wenn die Liste klein genug zum Kopieren ist, ist es einfacher, sie einfach in ein neues List<human> zu kopieren, wie Paw herausfand.

0

Wenn ich kann Ihr List<Joe> joes als verallgemeinert werden ...

List<Human> humans = joes; 

... die beiden Referenzen humans und joes sind, nun weiter auf die exakt gleiche Liste zeigt. Der Code, der der obigen Zuweisung folgt, hat keine Möglichkeit, das Einfügen/Hinzufügen einer Instanz eines anderen menschlichen Typs, etwa eines Klempners, in die Liste zu verhindern.Da class Plumber: Human {}

humans.Add(new Plumber()); // Add() now accepts any Human not just a Joe 

der Liste, die humans bezieht sich auf nun beide Joes und Installateure enthält. Beachten Sie, dass das gleiche Listenobjekt immer noch mit der Referenz joes bezeichnet wird. Jetzt, wenn ich die Referenz joes verwende, um aus dem Listenobjekt zu lesen, könnte ich einen Klempner anstelle eines Joes herausbringen. Es ist nicht bekannt, dass Klempner und Joe implizit ineinander übersetzbar sind ... also macht die Tatsache, dass ich einen Klempner anstelle eines Joes von der Liste bekomme, die Art der Sicherheit zunichte. Ein Klempner ist durch einen Verweis auf eine Liste von Joes sicherlich nicht willkommen.

In den letzten Versionen von C# ist es jedoch möglich, diese Einschränkung für eine generische Klasse/Sammlung zu umgehen, indem eine generische Schnittstelle implementiert wird, deren Typparameter einen Modifizierer out enthält. Sagen wir, wir haben jetzt ABag<T> : ICovariable<out T>. Der out-Modifizierer beschränkt die T nur auf Ausgabepositionen (z. B. Methodenrückgabetypen). Sie können kein T in den Beutel geben. Sie können sie nur daraus lesen. Dies ermöglicht es uns, Joes auf eine ICovariable<Human> zu verallgemeinern, ohne dass wir uns Gedanken darüber machen müssen, einen Plumber hinein zu fügen, da die Schnittstelle das nicht erlaubt. Wir können jetzt schreiben ...

ICovariable<Human> humans = joes ; // now its good ! 
humans.Add(new Plumber()); // error