2013-06-06 13 views
24

ich eine Liste von DerivedClass an eine Funktion zu übergeben bin versucht, die eine Liste von BaseClass nimmt, aber ich habe den Fehler:nicht aus Liste konvertieren Kann <DerivedClass><BaseClass> zur Liste

cannot convert from 
'System.Collections.Generic.List<ConsoleApplication1.DerivedClass>' 
to 
'System.Collections.Generic.List<ConsoleApplication1.BaseClass>' 

Jetzt konnte ich gegossen mein List<DerivedClass> zu einem List<BaseClass>, aber ich fühle mich nicht wohl, wenn ich nicht verstehe, warum der Compiler dies nicht erlaubt.

Erklärungen, die ich gefunden habe, haben einfach gesagt, dass es Typsicherheit irgendwie verletzt, aber ich sehe es nicht. Kann mir jemand helfen?

Wie groß ist das Risiko, dass der Compiler die Konvertierung von List<DerivedClass> zu List<BaseClass> zulässt?


Hier ist meine SSCCE:

class Program 
{ 
    public static void Main() 
    { 
     BaseClass bc = new DerivedClass(); // works fine 
     List<BaseClass> bcl = new List<DerivedClass>(); // this line has an error 

     doSomething(new List<DerivedClass>()); // this line has an error 
    } 

    public void doSomething(List<BaseClass> bc) 
    { 
     // do something with bc 
    } 
} 

class BaseClass 
{ 
} 

class DerivedClass : BaseClass 
{ 
} 
+0

Die Klasse von seiner Basis ergibt sich aber nicht die 'Liste ' leitet sich von 'Liste '. –

+0

@TimSchmelter Bedeutet das, dass diese Art der Vererbung möglicherweise zu einem Feature gemacht werden kann, bei dem man annehmen kann, dass 'Generic ' von 'Generic ' abgeleitet werden kann? oder würde ein solches Feature Probleme verursachen? –

+1

Es ist einfach nicht so implementiert. http://marcgravell.blogspot.fr/2009/02/what-c-40-covariance-doesn-do.html –

Antwort

37

funktionieren Es ist, weil List<T>in-variant ist, nicht co-variant, so dass Sie IEnumerable<T> ändern sollte, die co-variant unterstützt, sollte es funktionieren:

IEnumerable<BaseClass> bcl = new List<DerivedClass>(); 
public void doSomething(IEnumerable<BaseClass> bc) 
{ 
    // do something with bc 
} 

Informationen über co-variant in generic

+0

Ab .NET 4.5 gibt es auch 'IReadOnlyList ', die verwendet werden können, wenn die Methode wahlfreien Zugriff auf das Auflistungselement benötigt. – tia

11

Das Verhalten Sie beschreiben genannt wird Kovarianz – wenn A ist B, dann List<A>istList<B>.

Für veränderbare Typen wie List<T> ist das jedoch grundsätzlich unsicher.

Wäre dies möglich gewesen, wäre die Methode in der Lage, eine new OtherDerivedClass() zu einer Liste hinzuzufügen, die eigentlich nur DerivedClass halten kann.

Covariance ist sicher auf unveränderlichen Typen, obwohl .NET nur in Schnittstellen und Delegaten unterstützt.
Wenn Sie den List<T> Parameter IEnumerable<T> ändern, dass

+0

@SamIam übergeben werden soll: Nein; Sie versuchen, eine "Liste " zu übergeben. Eine "Liste " zu übergeben, die "Abgeleitete" enthält, würde gut funktionieren. – SLaks

+0

Wenn ich also meine 'Liste ' in 'Liste ' umwandle oder konvertiere, bevor ich sie übergebe, wäre das okay? –

+1

@SamIam: Nein. Der springende Punkt ist, dass es grundsätzlich unsicher ist, 'List ' in 'List ' zu schreiben. Wenn Sie zu Beginn eine 'List ' erstellen, wird es funktionieren. – SLaks

1

Wenn könnten Sie schreiben

List<BaseClass> bcl = new List<DerivedClass>(); 

könnten Sie

var instance = new AnotherClassInheritingFromBaseClass(); 
bc1.Add(instance); 

rufen Sie eine Instanz hinzufügen, die auf der Liste kein DerivedClass ist.

2

Wenn Sie eine Klasse haben, die von einer Basisklasse abgeleitet ist, werden alle Container dieser Klassen nicht automatisch abgeleitet. Sie können also nicht einfach eine List<Derived> an eine List<Base> übertragen.

Verwenden .Cast<T>() eine neue Liste zu erstellen, in dem jedes Objekt wird auf die Basisklasse zurückgeworfen:

List<MyDerived> list1 = new List<MyDerived>(); 
List<MyBase> list2 = list1.Cast<MyBase>().ToList(); 

Beachten Sie, dass dies eine neue Liste, nicht eine gegossene Version Ihrer ursprünglichen Liste, so dass Operationen auf diesem Die neue Liste spiegelt nicht die ursprüngliche Liste wider. Operationen an den enthaltenen Objekten spiegeln sich jedoch wider.

+1

'Liste list2 = neue Liste (list1);' scheint mir sauberer zu sein als 'Cast'. – Brian

38

Explanations that I have found have simply said that it violates type safety somehow, but I'm not seeing it. What is the risk of the compiler allowing conversion from List<DerivedClass> to List<BaseClass> ?

Diese Frage wird fast jeden Tag gestellt.

A List<Mammal> kann nicht auf eine List<Animal> konvertiert werden, da Sie eine Eidechse in eine Liste von Tieren setzen können. A List<Mammal> kann nicht zu einem List<Giraffe> konvertiert werden, da dort ein Tiger in der Liste sein kann bereits.

Daher hat List<T>invariant in T.

jedoch sein kann List<Mammal> zu IEnumerable<Animal> umgewandelt werden (wie von C# 4.0), da es keine Methode auf IEnumerable<Animal> ist, die eine lizard hinzufügt. IEnumerable<T> ist covariant in T.

+4

+1 Eine einfache zoologische Erklärung, die alles erklärt. (Es gibt keine Methode auf IEnumerable , die eine Eidechse hinzufügt -> Ich werde nur für den Fall überprüfen;)) –

+0

Ich fühle mich dumm ... die Giraffe scheint man offensichtlich, aber warum sollten Sie Säugetiere nicht passieren können etwas, das mit Tieren umgehen kann, nur weil es auch mit Nicht-Säugetieren umgehen kann? – MGOwen

+2

@ MGOwen: Sie haben eine Liste von Säugetieren. Sie beschließen, es als eine Liste von Tieren zu behandeln. Du legst eine Eidechse in die Liste der Tiere. ** Aber es ist wirklich eine Liste von Säugetieren **. Jetzt haben Sie eine Liste von Säugetieren mit einer Eidechse darin, die die Erwartung verletzt, dass eine Liste von Säugetieren nur Säugetiere enthält. Die Schlussfolgerung ist eine Verletzung des Typsystems, so dass einer dieser Schritte illegal sein muss. Es ist die erste: Sie können eine Liste von Säugetieren nicht als eine Liste von Tieren behandeln. –

0

Eine Lösung war ich verwendet, um eine Erweiterungsklasse zu erstellen:

public static class myExtensionClass 
{ 
    public void doSomething<T>(List<BaseClass> bc) where T: BaseClass 
    { 
     // do something with bc 
    } 
} 

Es verwendet generisch, aber wenn man es so nennen Sie müssen, da Sie die Klasse nicht angeben hat dem Compiler bereits "gesagt", dass der Typ der erweiterten Klasse gleich ist.

Sie es nennen würde auf diese Weise:

List<DerivedClass> lst = new List<DerivedClass>(); 
lst.doSomething(); 
Verwandte Themen