Während wir von der Basisklasse / Schnittstelle erben können, warum können wir nicht deklarieren List<>
, dass dieselbe Klasse / Schnittstelle verwendet wird?
interface A
{ }
class B : A
{ }
class C : B
{ }
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
List<A> listOfA = new List<C>(); // compiler Error
}
}
Gibt es einen Weg herum?
Antworten:
Um dies zu erreichen, müssen Sie die Liste durchlaufen und die Elemente umwandeln. Dies kann mit ConvertAll erfolgen:
Sie können auch Linq verwenden:
quelle
ConvertAll
oderCast
?Verwenden Sie zunächst keine unverständlichen Klassennamen wie A, B, C. Verwenden Sie Tier, Säugetier, Giraffe oder Nahrung, Obst, Orange oder etwas, bei dem die Beziehungen klar sind.
Ihre Frage lautet dann: "Warum kann ich einer Variablen vom Typ Tierliste keine Liste von Giraffen zuweisen, da ich einer Variablen vom Typ Tier eine Giraffe zuweisen kann?"
Die Antwort lautet: Angenommen, Sie könnten. Was könnte dann schief gehen?
Nun, Sie können einen Tiger zu einer Liste von Tieren hinzufügen. Angenommen, Sie können eine Liste von Giraffen in eine Variable einfügen, die eine Liste von Tieren enthält. Dann versuchen Sie, dieser Liste einen Tiger hinzuzufügen. Was geschieht? Möchten Sie, dass die Liste der Giraffen einen Tiger enthält? Willst du einen Absturz? oder möchten Sie, dass der Compiler Sie vor dem Absturz schützt, indem er die Zuweisung überhaupt illegal macht?
Wir wählen letzteres.
Diese Art der Umwandlung wird als "kovariante" Umwandlung bezeichnet. In C # 4 können Sie kovariante Konvertierungen für Schnittstellen und Delegaten durchführen, wenn bekannt ist, dass die Konvertierung immer sicher ist . Weitere Informationen finden Sie in meinen Blog-Artikeln zu Kovarianz und Kontravarianz. (Am Montag und Donnerstag dieser Woche wird es ein neues zu diesem Thema geben.)
quelle
IEnumerable
anstelle von a durchführenList
? dh:List<Animal> listAnimals = listGiraffes as List<Animal>;
ist nicht möglich,IEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>
funktioniert aber .IEnumerable<T>
undIEnumerator<T>
beide sind als sicher für die Kovarianz markiert, und der Compiler hat dies überprüft.Um die großartige Erklärung von Eric zu zitieren
Aber was ist, wenn Sie sich für einen Laufzeitabsturz anstelle eines Kompilierungsfehlers entscheiden möchten? Normalerweise verwenden Sie Cast <> oder ConvertAll <>, aber dann treten zwei Probleme auf: Es wird eine Kopie der Liste erstellt. Wenn Sie der neuen Liste etwas hinzufügen oder daraus entfernen, wird dies nicht in der ursprünglichen Liste angezeigt. Und zweitens gibt es einen großen Leistungs- und Speicherverlust, da eine neue Liste mit den vorhandenen Objekten erstellt wird.
Ich hatte das gleiche Problem und habe daher eine Wrapper-Klasse erstellt, die eine generische Liste umwandeln kann, ohne eine völlig neue Liste zu erstellen.
In der ursprünglichen Frage könnten Sie dann verwenden:
und hier die Wrapper-Klasse (+ eine Erweiterungsmethode CastList zur einfachen Verwendung)
quelle
Wenn Sie
IEnumerable
stattdessen verwenden, wird es funktionieren (zumindest in C # 4.0 habe ich frühere Versionen nicht ausprobiert). Dies ist nur eine Besetzung, natürlich wird es immer noch eine Liste sein.Anstatt -
List<A> listOfA = new List<C>(); // compiler Error
Verwenden Sie im Originalcode der Frage -
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
quelle
List<A> listOfA = new List<C>(); // compiler Error
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
Was den Grund betrifft, warum es nicht funktioniert, kann es hilfreich sein, Kovarianz und Kontravarianz zu verstehen .
Um zu zeigen, warum dies nicht funktionieren sollte, wurde der von Ihnen angegebene Code geändert:
Sollte das funktionieren? Das erste Element in der Liste ist vom Typ "B", der Typ des DerivedList-Elements ist jedoch C.
Nehmen wir nun an, wir möchten wirklich nur eine generische Funktion erstellen, die mit einer Liste eines Typs arbeitet, der A implementiert, aber es ist uns egal, um welchen Typ es sich handelt:
quelle
Sie können nur in schreibgeschützte Listen umwandeln. Beispielsweise:
Und Sie können dies nicht für Listen tun, die das Speichern von Elementen unterstützen. Der Grund dafür ist:
Was jetzt? Denken Sie daran, dass listObject und listString tatsächlich dieselbe Liste sind, sodass listString jetzt ein Objektelement hat - es sollte nicht möglich sein und es ist nicht möglich.
quelle
Ich persönlich mag es, Bibliotheken mit Erweiterungen der Klassen zu erstellen
quelle
Weil C # diese Art von nicht zulässt
ErbeUmwandlung im Moment .quelle
Dies ist eine Erweiterung von BigJims brillanter Antwort .
In meinem Fall hatte ich eine
NodeBase
Klasse mit einemChildren
Wörterbuch, und ich brauchte eine Möglichkeit, generisch O (1) -Suchen von den Kindern durchzuführen. Ich habe versucht, ein privates Wörterbuchfeld im Getter von zurückzugebenChildren
, daher wollte ich natürlich teures Kopieren / Iterieren vermeiden. Deshalb habe ich Bigjims Code verwendet, um dasDictionary<whatever specific type>
in ein generisches umzuwandelnDictionary<NodeBase>
:Das hat gut funktioniert. Schließlich stieß ich jedoch auf nicht verwandte Einschränkungen und erstellte schließlich eine abstrakte
FindChild()
Methode in der Basisklasse, die stattdessen die Suche durchführen würde. Wie sich herausstellte, war das gegossene Wörterbuch damit überhaupt nicht mehr erforderlich. (Ich konnte esIEnumerable
für meine Zwecke durch ein einfaches ersetzen .)Die Frage, die Sie möglicherweise stellen (insbesondere wenn die Leistung ein Problem darstellt, das Ihnen die Verwendung von
.Cast<>
oder verbietet.ConvertAll<>
), lautet:"Muss ich wirklich die gesamte Sammlung besetzen oder kann ich eine abstrakte Methode verwenden, um das für die Ausführung der Aufgabe erforderliche Spezialwissen zu speichern und so den direkten Zugriff auf die Sammlung zu vermeiden?"
Manchmal ist die einfachste Lösung die beste.
quelle
Sie können auch das
System.Runtime.CompilerServices.Unsafe
NuGet-Paket verwenden, um einen Verweis auf dasselbe zu erstellenList
:Hammer
Anhand des obigen Beispiels können Sie mit dertools
Variablen auf die vorhandenen Instanzen in der Liste zugreifen . Das Hinzufügen vonTool
Instanzen zur Liste löst eineArrayTypeMismatchException
Ausnahme aus, datools
auf dieselbe Variable wie verwiesen wirdhammers
.quelle
Ich habe diesen ganzen Thread gelesen und möchte nur darauf hinweisen, was mir als Inkonsistenz erscheint.
Der Compiler verhindert, dass Sie die Zuweisung mit Listen ausführen:
Aber der Compiler ist mit Arrays vollkommen in Ordnung:
Das Argument, ob die Zuordnung als sicher bekannt ist, fällt hier auseinander. Die Zuordnung, die ich mit dem Array vorgenommen habe, ist nicht sicher . Um das zu beweisen, wenn ich dem folge:
Ich erhalte eine Laufzeitausnahme "ArrayTypeMismatchException". Wie erklärt man das? Wenn der Compiler mich wirklich daran hindern möchte, etwas Dummes zu tun, hätte er mich daran hindern sollen, die Array-Zuweisung vorzunehmen.
quelle