Warum kann in C # ein List <string> -Objekt nicht in einer List <object> -Variable gespeichert werden?


85

Es scheint, dass ein List-Objekt nicht in einer List-Variablen in C # gespeichert und nicht einmal explizit auf diese Weise umgewandelt werden kann.

List<string> sl = new List<string>();
List<object> ol;
ol = sl;

Ergebnisse in Can nicht implizit konvertiert Typ System.Collections.Generic.List<string>zuSystem.Collections.Generic.List<object>

Und dann...

List<string> sl = new List<string>();
List<object> ol;
ol = (List<object>)sl;

Ergebnisse in Typ kann nicht konvertiert werden System.Collections.Generic.List<string>inSystem.Collections.Generic.List<object>

Natürlich können Sie dies tun, indem Sie alles aus der Zeichenfolgenliste herausziehen und einzeln wieder einfügen, aber es ist eine ziemlich komplizierte Lösung.


5
Dies wird sich mit C # 4.0 ändern, sodass Sie möglicherweise nach Kovarianz und Kontravarianz suchen möchten. Es wird solche Dinge auf typsichere Weise ermöglichen.
John Leidegren


7
John> C # 4 wird dies nicht zulassen. Denken Sie an ol.Add (neues Objekt ());
Guillaume

Es wird von Jon Skeet hier beantwortet: stackoverflow.com/a/2033921/1070906
JCH2k

Antworten:


39

Stellen Sie sich das so vor: Wenn Sie eine solche Besetzung durchführen und dann ein Objekt vom Typ Foo zur Liste hinzufügen, ist die Liste der Zeichenfolgen nicht mehr konsistent. Wenn Sie die erste Referenz iterieren würden, würden Sie eine Klassenumwandlungsausnahme erhalten, da die Foo nach dem Aufrufen der Foo-Instanz nicht in eine Zeichenfolge konvertiert werden konnte!

Als Randnotiz denke ich, dass es wichtiger wäre, ob Sie die umgekehrte Besetzung machen können oder nicht:

List<object> ol = new List<object>();
List<string> sl;
sl = (List<string>)ol;

Ich habe C # schon eine Weile nicht mehr verwendet, daher weiß ich nicht, ob das legal ist, aber diese Art der Besetzung ist tatsächlich (möglicherweise) nützlich. In diesem Fall wechseln Sie von einer allgemeineren Klasse (Objekt) zu einer spezifischeren Klasse (Zeichenfolge), die sich von der allgemeinen Klasse erstreckt. Auf diese Weise verletzen Sie nicht die Liste der Objekte, wenn Sie der Liste der Zeichenfolgen hinzufügen.

Weiß jemand oder kann er testen, ob eine solche Besetzung in C # legal ist?


4
Ich denke, Ihr Beispiel leidet unter dem gleichen Problem. Was passiert, wenn ol etwas enthält, das keine Zeichenfolge ist? Das Problem ist, dass einige Methoden in List gut funktionieren würden, z. B. Hinzufügen / Einfügen. Aber das Iterieren könnte ein echtes Problem sein.
Chris Ammerman

3
Eric Lippert hat eine großartige Reihe von Blog-Posts zu diesem Thema: Warum es funktionieren könnte, generischen Methoden Kovarianz- und Kontravarianz-Beschränkungen hinzuzufügen, aber möglicherweise nie so, wie wir es auf Klassenebene möchten. is.gd/3kQc
Chris Ammerman

@ChrisAmmerman: Wenn oletwas drin wäre, das kein String ist, würde ich wahrscheinlich erwarten, dass die Besetzung zur Laufzeit fehlschlägt. Aber wo Sie wirklich in Schwierigkeiten geraten würden, wäre, wenn die Besetzung erfolgreich wäre und dann etwas hinzugefügt würde ol, das keine Zeichenfolge ist. Da Sie slauf dasselbe Objekt verweisen, enthält Ihr Objekt jetzt List<string>eine Nicht-Zeichenfolge. Das Addist das Problem, das ich denke , rechtfertigt , warum dieser Code wird nicht kompiliert, aber es wird kompilieren , wenn Sie ändern List<object> olzu IEnumerable<object> ol, was nicht hat Add. (Ich habe dies in C # 4 überprüft.)
Tim Goodman

Mit dieser Änderung wird kompiliert, aber ein ausgelöst, InvalidCastExceptionda der Laufzeit-Typ von olimmer noch ist List<object>.
Tim Goodman

36

Wenn Sie .NET 3.5 verwenden, sehen Sie sich die Enumerable.Cast-Methode an. Es ist eine Erweiterungsmethode, mit der Sie sie direkt in der Liste aufrufen können.

List<string> sl = new List<string>();
IEnumerable<object> ol;
ol = sl.Cast<object>();

Es ist nicht genau das, wonach Sie gefragt haben, aber es sollte den Trick machen.

Bearbeiten: Wie von Zooba bemerkt, können Sie dann ol.ToList () aufrufen, um eine Liste zu erhalten


14

Sie können nicht zwischen generischen Typen mit unterschiedlichen Typparametern wechseln. Spezialisierte generische Typen sind nicht Teil desselben Vererbungsbaums und daher auch nicht verwandte Typen.

So führen Sie dies vor NET 3.5 durch:

List<string> sl = new List<string>();
// Add strings to sl

List<object> ol = new List<object>();

foreach(string s in sl)
{
    ol.Add((object)s);  // The cast is performed implicitly even if omitted
}

Verwenden von Linq:

var sl = new List<string>();
// Add strings to sl

var ol = new List<object>(sl.Cast<object>());

// OR
var ol = sl.Cast<object>().ToList();

// OR (note that the cast to object here is required)
var ol = sl.Select(s => (object)s).ToList();

11

Der Grund ist, dass eine generische Klasse wie List<>für die meisten Zwecke extern als normale Klasse behandelt wird. zB wenn du sagst List<string>()der Compiler sagtListString() (was Strings enthält). [Technische Leute: Dies ist eine extrem einfach englischsprachige Version dessen, was los ist]

Folglich kann der Compiler offensichtlich nicht intelligent genug sein, um einen ListString in ein ListObject zu konvertieren, indem er die Elemente seiner internen Sammlung umwandelt.

Aus diesem Grund gibt es Erweiterungsmethoden für IEnumerable wie Convert (), mit denen Sie problemlos Konvertierungen für die in einer Sammlung gespeicherten Elemente bereitstellen können. Dies kann so einfach sein wie das Umwandeln von einem zum anderen.


6

Dies hat viel mit Kovarianz zu tun, z. B. werden generische Typen als Parameter betrachtet, und wenn die Parameter nicht ordnungsgemäß in einen spezifischeren Typ aufgelöst werden, schlägt die Operation fehl. Dies impliziert, dass Sie wirklich nicht in ein allgemeineres typähnliches Objekt umwandeln können. Und wie von Rex angegeben, konvertiert das List-Objekt nicht jedes Objekt für Sie.

Vielleicht möchten Sie stattdessen den ff-Code ausprobieren:

List<string> sl = new List<string>();
//populate sl
List<object> ol = new List<object>(sl);

oder:

List<object> ol = new List<object>();
ol.AddRange(sl);

ol kopiert (theoretisch) problemlos den gesamten Inhalt von sl.


5

Ja, ab .NET 3.5 können Sie:

List<string> sl = new List<string>();
List<object> ol = sl.Cast<object>().ToList();



1

Das ist eigentlich so, dass Sie nicht versuchen, ein seltsames "Objekt" in Ihre "alte" Listenvariante einzufügen (wie List<object>es zu erlauben scheint) - weil Ihr Code dann abstürzen würde (weil die Liste wirklich ist)List<string> Objekte vom Typ String und nur Objekte vom Typ String akzeptiert ). Aus diesem Grund können Sie Ihre Variable nicht in eine allgemeinere Spezifikation umwandeln.

Unter Java ist es umgekehrt, Sie haben keine Generika und stattdessen ist alles zur Laufzeit eine Objektliste, und Sie können wirklich jedes seltsame Objekt in Ihre angeblich streng typisierte Liste einfügen. Suchen Sie nach "Reified Generics", um eine breitere Diskussion über das Problem von Java zu erhalten ...


1

Eine solche Kovarianz bei Generika wird nicht unterstützt, aber Sie können dies tatsächlich mit Arrays tun:

object[] a = new string[] {"spam", "eggs"};

C # führt Laufzeitprüfungen durch, um zu verhindern, dass Sie beispielsweise ein intin setzen a.


0

Hier ist eine weitere Pre-.NET 3.5-Lösung für jeden IList, dessen Inhalt implizit umgewandelt werden kann.

public IList<B> ConvertIList<D, B>(IList<D> list) where D : B
{
    List<B> newList = new List<B>();

    foreach (D item in list)
    {
        newList.Add(item);
    }

    return newList;
}

(Basierend auf Zoobas Beispiel)


0

Ich habe ein:

private List<Leerling> Leerlingen = new List<Leerling>();

Und ich wollte es mit Daten füllen, die in einem gesammelt wurden. List<object> Was für mich schließlich funktionierte, war dieses:

Leerlingen = (List<Leerling>)_DeserialiseerLeerlingen._TeSerialiserenObjecten.Cast<Leerling>();

.Castes auf die Art wollen Sie eine bekommen IEnumerablevon dieser Art, dann die Typumwandlung IEnemuerableauf die List<>Sie wollen.


0

Mm, dank vorheriger Kommentare habe ich zwei Möglichkeiten gefunden, es herauszufinden. Der erste besteht darin, die Zeichenfolgenliste der Elemente abzurufen und sie dann in die IEnumerable-Objektliste umzuwandeln:

IEnumerable<object> ob;
List<string> st = new List<string>();
ob = st.Cast<object>();

Und die zweite besteht darin, den IEnumerable-Objekttyp zu vermeiden, einfach die Zeichenfolge in den Objekttyp umzuwandeln und dann die Funktion "toList ()" im selben Satz zu verwenden:

List<string> st = new List<string>();
List<object> ob = st.Cast<object>().ToList();

Ich mag mehr den zweiten Weg. Ich hoffe das hilft.


0
List<string> sl = new List<string>();
List<object> ol;
ol = new List<object>(sl);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.