Laden...

Objekt auf ein generisches Interface casten, ohne den Typparameter zu kennen?

Erstellt von Alan vor 10 Jahren Letzter Beitrag vor 10 Jahren 2.871 Views
Thema geschlossen
A
Alan Themenstarter:in
22 Beiträge seit 2013
vor 10 Jahren
Objekt auf ein generisches Interface casten, ohne den Typparameter zu kennen?

Hallo zusammen,

ich arbeite derzeit mit einer externen Bibliothek, die recht allgemein gehalten ist und bei vielen Methoden einfach nur "object" zurück gibt. Darum hat sich jetzt folgende Situation ergeben:

object theObjectToCast = /* Bibliotheks-Aufruf */;
IEnumerable<object> collection = (/* hier! */)theObjectToCast;

foreach(object content in collection){
	// hier soll über das IEnumerable iteriert werden
}

Ich weiß ganz genau (aufgrund der API-Dokumentation der Bibliothek und meinem Parameter beim Aufruf) dass das "theObjectToCast" das Interface "IEnumerable" implementiert (ich habe nachgeschaut, der Laufzeit-Typ der zurück kommt ist ein Standard-HashSet). Den generischen Typ-Parameter kenne ich allerdings nicht, darum verwende ich "object".

Wenn ich allerdings versuche, das /* hier! */ mit einem direkten Cast auf IEnumerable<object> zu ersetzen, erhalte ich einen Laufzeit-Fehler.

Meine Frage also: wie kann ich einen generischen Typ auf ein generisches Interface casten, sodass das generische Interface als Typ-Parameter einfach nur "object" hat?

Gruß,

Alan

PS: Tut mir leid falls meine Formulierung etwas schwammig ist, ich kenne Generics bislang nur von Java, und dort läuft es ja total anders wegen der Type Erasure.

5.941 Beiträge seit 2005
vor 10 Jahren

Hallo Alan

Und wie lautet der Laufzeitfehler?

Caste mal in das Interface IEnumerable, ohne Generics. IEnumerable arbeitet in jedem Fall mit object, also passt das ja für dich.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Alan,

auch wenn es bei IEnumerable<> möglich ist, das untypisierte IEnumerable zu verwenden, ist im allgemeinen Fall die Frage, warum du den Typparameter nicht kennst. Eigentlich solltest du den kennen. Wenn du theObjectToCast z.B. als Parameter der Methode bekommst, wäre es sinnvoll, wenn der Parameter schon korrekt typisiert übergeben wird, z.B.

private void M<T> (IEnumerable<T> theObjectToCast)

Ansonsten sollte es bei IEnumerable<T>-Schnittstelle möglich sein, Kovarianz zu verwenden, denn der Typparameter von IEnumerable<> ist als out definiert, siehe auch Kovarianz und Kontravarianz in Generika.

herbivore

A
Alan Themenstarter:in
22 Beiträge seit 2013
vor 10 Jahren

Hallo zusammen,

ich danke euch beiden schonmal für die schnellen Antworten, ich war die letzten Tage viel unterwegs und bin daher noch nicht dazu gekommen, mich um dieses Problem zu kümmern.

Hier die angefragte Ausnahme, die sich aus einem direkten Cast auf "IEnumerable<object>" ergibt:

Fehlermeldung:
Das Objekt des Typs "System.Collections.Generic.HashSet1[System.Int16]&quot; kann nicht in Typ &quot;System.Collections.Generic.IEnumerable1[System.Object]" umgewandelt werden.

... was auch immer die Ursache für diesen Fehler ist. Kann es sein, dass System.Int16 keine Subklasse von System.Object ist? Eigentlich nicht oder? Primitive Typen im klassischen Sinn wie in Java existieren meines Wissens nach in C# nicht.

Zur Info: die Library die ich verwende, ist die .net-Implementierung von XStream, also ein XML-Deserializer. Daher kenne ich die genauen Objekte nicht, die von XStream zurück kommen. Meine Anwendung verlangt es zudem, dass die Typen, die XStream und meine Anwendung verwenden, dynamisch per Assembly.Load(...) geladen werden müssen - ich kann also keinesfalls den Typen des Hashsets vorher schon angeben. In dem Beispiel, in dem es schief geht, handelt es sich wie oben angegeben um ein HashSet<System.Int16>. Aber das kann theoretisch auch irgendein Typ sein, darum mein Ansatz mit IEnumerable<object>.

Wenn jemand weiß, wie und warum der obige Laufzeitfehler zustande kommt, wäre ich sehr dankbar, ich stehe hier als Java-Entwickler wirklich wie der sprichwörtliche Ochse vor dem Berg und frage mich, was da eigentlich passiert.... ^^'

@Herbivore: Danke für den Link, ich werde mir das jetzt mal durch lesen, eventuell beinhaltet es eine Erklärung für mein Problem. Ich habe schon mehrere Publikationen von Microsoft (und anderen) zum Thema Generics in C# gelesen, aber keines der Papers konnte Licht auf mein Problem hier werfen.

Gruß,

Alan

EDIT: Okay, jetzt bin ich endgültig verwirrt. Ich habe gerade folgendes ausprobiert:


            object obj = new Int16();      // das geht problemlos
            IEnumerable<object> hashSet = new HashSet<Int16>(); // typecast-Error wie oben beschrieben
            HashSet<object> test = new HashSet<Int16>();  // geht auch nicht

... und verstehe gerade die Welt nicht mehr X(

5.941 Beiträge seit 2005
vor 10 Jahren

Hallo Alan

Der Typ von deinem "theObjectToCast" ist laut der Fehlermeldung folgender:


System.Collections.Generic.HashSet`1[System.Int16]

Das wäre in einem Cast ausgeschrieben:


HashSet<Int16> typedSet = (HashSet<Int16>)theObjectToCast;

Also caste auf diesen, das ist die einfachste Lösung.
Die Co- und Contravarianz, die von herbivore angesprochen wurde, hilft, einem generellen auf einen spezifischen oder umgekehrt generischen Typ bzw. dessen Typargument zu casten.

Ohne Co- und Contravarianz ist das nicht direkt möglich. Es geht aber per .Cast()-Methode von LINQ; diese castet jeden Eintrag einer Menge einzeln und gibt eine neue Menge zurück.

z.B.


List<object> objectList = ...
List<Haus> hausList = objectList.Cast<Haus>().ToList();

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

A
Alan Themenstarter:in
22 Beiträge seit 2013
vor 10 Jahren

Hallo Peter,

danke für deinen Beitrag! Ich habe es mittlerweile geschafft, über mein "unbekanntes" HashSet zu iterieren, indem ich einfach anstelle des IEnumerable<object> und des casts das "dynamic"-Keyword von C# verwendet habe. Das funktioniert absolut problemlos. Es ist vielleicht nicht die sauberste Variante und garantiert alles andere als typsicher, aber meinen Zweck erfüllt es trotzdem einstweilen.

Dennoch, das hier:

object obj = new Int16();      // das geht problemlos
            IEnumerable<object> hashSet = new HashSet<Int16>(); // typecast-Error wie oben beschrieben
            HashSet<object> test = new HashSet<Int16>();  // geht auch nicht

... ist mir ein absolutes Rätsel und ich würde mich sehr für eine Begründung interessieren wieso das nicht möglich ist. IEnumerable<T> und alle seine Subklassen sollten doch kovariant sein laut Beschreibung von MSDN? Da Int16 eine Subklasse von Object ist (was der Code oben auch zeigt), sollte es doch möglich sein, einem IEnumerable<object> ein HashSet<Int16> zu zuweisen oder nicht?

Danke,

Alan

5.941 Beiträge seit 2005
vor 10 Jahren

Hallo Alan

Mir ist nicht klar, wieso du das überhaupt untypisiert machen willst.
Wieso nicht auf den eigentlichen Typ HashSet<Int16> casten und darüber iterieren?

Kommt nicht immer dasselbe zurück?

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

A
Alan Themenstarter:in
22 Beiträge seit 2013
vor 10 Jahren

Kommt nicht immer dasselbe zurück?

Nein, genau das ist ja gerade das Problem 😃 Mein Code muss mit allem zurecht kommen, was die XML-Deserialisierung von XStream ergibt. Im Prinzip muss ich den "Objektbaum", der sich aus dem deserialisierten XML ergibt, auf einer graphischen Oberfläche darstellen. Zu diesem Zweck soll über das Datenmodell iteriert werden. Da es sich hier wirklich um beliebige Daten handeln kann, ist es unmöglich, schon zur Compile-Zeit einen anderen Typ als "object" festzulegen, leider.

S
417 Beiträge seit 2008
vor 10 Jahren

Hallo,

hier kommt zum Tragen, dass für Wertetype wie int, double etc. keine Varianz unterstützt wird, sondern nur für Referenztypen. Lies dir am Besten mal die FAQ zu Co- und Contravariance durch:
Covariance and Contravariance FAQ

A
Alan Themenstarter:in
22 Beiträge seit 2013
vor 10 Jahren

hier kommt zum Tragen, dass für Wertetype wie int, double etc. keine Varianz unterstützt wird, sondern nur für Referenztypen.

Ach du meine Güte. Das nenne ich mal einen echten Stolperstrick - darauf wäre ich im Leben nicht gekommen! Ich habe zwar die FAQ, die du verlinkt hast, einmal überflogen, allerdings konnte ich nichts finden was zu meinem Problem gepasst hat. Dein Satz passt allerdings wie die Faust auf's Auge und jetzt wo ich wusste, wonach ich suchen muss, habe ich es auch im FAQ gefunden. Eigentlich hätte ich da schon vor einiger Zeit dran gedacht, dass es daran liegen könnte, dass der Typ ein Primitive ist, aber da in C# selbst Literale von Integern Methoden ausführen können, habe ich den Gedanken wieder verworfen facepalm

Danke danke danke, jetzt ist mir einiges klar 😄

Gruß,

Alan

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Alan,

IEnumerable<T> und alle seine Subklassen sollten doch kovariant sein laut Beschreibung von MSDN?

nein, nicht alle Klasse die IEnumerable<T> implementieren, sind kovariant. Die Begründung ist immer die gleiche und wurde auch schon oft genug gegeben (bitte beachte [Hinweis] Wie poste ich richtig? Punkt 1.1, z.B. Forumssuche nach kovarian*).

Wenn sich HashSet<Int16> (unabhängig von der Werttyp-Problematik) auf HashSet<Object> casten lassen würde, dann könnte man auf das Ergebnis die Add-Methode mit der Signatur HashSet<Object>.Add (Object value) anwenden und könnte damit (versuchen) beliebige Objekte zu einem HashSet hinzufügen, das eigentlich nur Int16s aufnehmen kann und darf.

herbivore

Thema geschlossen