Laden...

Selektive Übergabe der Inhalte einer List<T1> in eine neue List<T2>

Erstellt von AlfaAlf vor 4 Jahren Letzter Beitrag vor 4 Jahren 699 Views
A
AlfaAlf Themenstarter:in
2 Beiträge seit 2020
vor 4 Jahren
Selektive Übergabe der Inhalte einer List<T1> in eine neue List<T2>

Hallo,

ich hänge seit einem Tag an quasi einer Codezeile fest und komme nicht weiter.
Folgende Situation:
Es existiert eine Liste List<IMyFunctionalBlock> allBlocksOfGrid, die alle Bausteine eines Raumschiffs im Spiel Space Engineers enthält.
Aus dieser Liste möchte ich jetzt alle Blöcke, welche das Interface IMyPowerProducer implementieren, in eine neue Liste kopieren. IMyPowerProducer erbt von IMyFunctionalBlock
In der Vergangenheit habe ich das immer mit foreach und if/switch gelöst. Allerdings wollte ich jetzt mal an die neuere kürzere Schreibweise mit Linq und Lamba-Ausdrücken erlernen.

Hier mal der Code:


            // read all power producing blocks out of the grid
            List<IMyPowerProducer> h2Engines = new List<IMyPowerProducer>();
            List<IMyBatteryBlock> batteries = new List<IMyBatteryBlock>();
            List<IMySolarPanel> solarPanels = new List<IMySolarPanel>();
            List<IMyReactor> reactors = new List<IMyReactor>();
            List<IMyPowerProducer> windTurbines = new List<IMyPowerProducer>();

            // List<IMyPowerProducer> h2Engines = allBlocksOfGrid.Where(b => b.DefinitionDisplayNameText.Contains("Hydrogen")) as List<IMyPowerProducer>;
            // List<IMyBatteryBlock> batteries = allBlocksOfGrid.Where(b => b.DefinitionDisplayNameText.Contains("Battery")) as List<IMyBatteryBlock>;
            // List<IMySolarPanel> solarPanels = allBlocksOfGrid.Where(b => b.DefinitionDisplayNameText.Contains("Solar")) as List<IMySolarPanel>;
            // List<IMyReactor> reactors = allBlocksOfGrid.Where(b => b.DefinitionDisplayNameText.Contains("Reactor")) as List<IMyReactor>;
            // List<IMyPowerProducer> windTurbines = allBlocksOfGrid.Where(b => b.DefinitionDisplayNameText.Contains("Wind")) as List<IMyPowerProducer>;
            allBlocksOfGrid.ForEach(b => Echo(b.DefinitionDisplayNameText));
            foreach (IMyFunctionalBlock block in allBlocksOfGrid)
            {
                switch (block.DefinitionDisplayNameText)
                {
                    case "Hydrogen Engine": h2Engines.Add(block as IMyPowerProducer); break;
                    case "Battery": batteries.Add(block as IMyBatteryBlock); break;
                    case "Solar Panel": solarPanels.Add(block as IMySolarPanel); break;
                    case "Small Reactor": reactors.Add(block as IMyReactor); break;
                    case "Large Reactor": reactors.Add(block as IMyReactor); break;
                    case "Wind Turbine": windTurbines.Add(block as IMyPowerProducer); break;
                }
            }

            Echo("H2-Engines:" + h2Engines.Count);
            Echo("Batteries:" + batteries.Count);
            Echo("Solar Panels:" + solarPanels.Count);
            Echo("Reactors:" + reactors.Count);
            Echo("Wind Turbines:" + windTurbines.Count);

So, wie es aktuell läuft, funktioniert es.
Die auskommentierten Zeilen waren mein Versuch mit Linq und Lambda.
Dies funktioniert auch, solange die neue Liste den gleichen Typ IMyFunctionalBlock hat. Dann kann ich hinten eben .ToList() dranhängen.
Nutze ich as List<IMyPowerProducer> wirft die Echo-Anweisung zur Überprüfung die Exception > Fehlermeldung:

Object reference not set to an instance of an object

Wie kann ich bei der selektiven Suche mit Rückgabe eines IEnumerable einen cast in eine Liste und in einen anderen Typ realisieren?
In den Beispielen, die man überall findet, wir ja immer mit var gearbeitet. Leider brauche ich den Typ IMyPowerProducer, da nur der die Methoden anbietet, die ich später benötige.
Die Objekte, die ich Suche implementieren auch den jeweils entsprechenden Typen.

Vielen Dank für Anregungen.

4.931 Beiträge seit 2008
vor 4 Jahren

Hallo und willkommen,

da nicht automatisch alle Objekte der Basisklasse dem der abgeleiteten Klasse entsprechen, kann dies nicht so kompilieren (denn dies muß ja typsicher funktionieren).
Du stellst ja nur zur Laufzeit sicher, daß nur die abgeleiteten Objekte (anhand des Namens!) das passende Interface besitzen.

Dafür gibt es explizit die Linq-Methode OfType<T>():


List<IMyPowerProducer> h2Engines = allBlocksOfGrid.OfType<IMyPowerProducer>().ToList<IMyPowerProducer>();

So brauchst du auch nicht über den Namen filtern.
Beachte außerdem, daß ich noch die Linq-Methode ToList<T>() verwende (und nicht den Cast mittels as), so daß dies für alle Enumerables, und nicht nur für Listen, funktioniert).

PS: Um Code nicht zu wiederholen, kannst du hier dann var benutzen:


var h2Engines = allBlocksOfGrid.OfType<IMyPowerProducer>().ToList<IMyPowerProducer>();

T
2.219 Beiträge seit 2008
vor 4 Jahren

Bei as List wird ja auch versucht den Eintrag in den Typen zu casten ohne einen Fehler zu werfen.
Stimmt der Typ nicht, dann knallt es nicht aber das Ziel Objekt ist null.
Hier musst du deinen Code prüfen und ggf. auch mal debuggen, dann wird auch klar warum dein Ansatz nicht funktioniert.

Nachtrag:
@Th69
Danke für den Tipp mit OfType, kann ich noch nicht 😃

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

A
AlfaAlf Themenstarter:in
2 Beiträge seit 2020
vor 4 Jahren

Super! Vielen Dank!

.ofType<>() war genau das was ich brauchte. Ich hab im Kopf so am casten gehangen, dass ich die Option anhand des Typs zu filtern nicht mehr gesehen habe.

So klappt es jetzt:


            // read all power producing blocks out of the grid
            var h2Engines = allBlocksOfGrid.OfType<IMyPowerProducer>().Where(b => b.DefinitionDisplayNameText.Contains("Hydrogen")).ToList<IMyPowerProducer>();
            var batteries = allBlocksOfGrid.OfType<IMyBatteryBlock>().ToList<IMyBatteryBlock>();
            var solarPanels = allBlocksOfGrid.OfType<IMySolarPanel>().ToList<IMySolarPanel>();
            var reactors = allBlocksOfGrid.OfType<IMyReactor>().ToList<IMyReactor>();
            var windTurbines = allBlocksOfGrid.OfType<IMyPowerProducer>().Where(b => b.DefinitionDisplayNameText.Contains("Wind")).ToList<IMyPowerProducer>();

            Echo("H2-Engines:" + h2Engines.Count);
            Echo("Batteries:" + batteries.Count);
            Echo("Solar Panels:" + solarPanels.Count);
            Echo("Reactors:" + reactors.Count);
            Echo("Wind Turbines:" + windTurbines.Count);

Debuggen ist leider nicht so einfach in dem Fall. Kurzer Exkurs:
Space Engineers ist ein Sandbox-Game, in dem man aus Blöcken Dinge bauen kann. Quasi wie bei bekannten Klemmbaustein-Spielwaren. Einer dieser Blöcke ist eine Kontrolleinheit, in die man C#-Code eingeben kann. Im Visual Studio kann ich diesen Code jedoch nicht debuggen, da mir der Coderahmen fehlt. Ich nutze es nur zur Syntax-Überprüfung.
Deswegen auch die Echo-Anweisungen. Damit kann ich mir im Spiel während der Code läuft Dinge anzeigen lassen, um den richtigen Verlauf zu überprüfen.