Laden...

Speichern polymorpher Objekte

Erstellt von ByteDevil vor 6 Jahren Letzter Beitrag vor 6 Jahren 2.265 Views
ByteDevil Themenstarter:in
132 Beiträge seit 2013
vor 6 Jahren
Speichern polymorpher Objekte

Hallo liebe Community,

gegeben seien folgende Klassen:


abstract class Fahrzeug { public int AnzahlRaeder { get; set; } }

class Auto : Fahrzeug
{
    public int AnzahlTueren { get; set; }
    public Auto(int raeder, int tueren) { AnzahlRaeder = raeder; AnzahlTueren = 4; }
}

class Motorrad : Fahrzeug
{
    public double Kettenspannung { get; set; }
    public Motorrad(double kettenspannung) { AnzahlRaeder = 2; Kettenspannung = kettenspannung; }
}

Welche so instanziiert werden:


List<Fahrzeug> fahrzeuge = new List<Fahrzeug>();
fahrzeuge.Add(new Auto(4, 4));
fahrzeuge.Add(new Motorrad(1));

Angenommen es wären noch deutlich mehr Klassen die auf diese Weise von Fahrzeug erben.
Wie würdet ihr diese Liste nun am besten in einer Datei speichern und sie dann wieder auslesen?

Möchte mir möglichst viel herumgewerfe mit den Unterklassen sparen. Gibt es da von MS vielleicht schon etwas fertiges?

Viele Grüße,
ByteDevil

T
2.224 Beiträge seit 2008
vor 6 Jahren

Ich würde die Liste nicht in einer Datei speichern.
Da du auch unterschiedliche Typen hast, die nur eine gemeinsame Oberklasse haben, würde ich die Typen schon aus Prinzip trennen.

Alternativ kannst du dir aber auch eine Container Klasse bauen, die pro Typen eine Property hat.
Dann kannst du eine Liste dieses Typen in eine Datei serailisieren.

Also z.B. sowas in der Art.


// .. Deine Klassen wie bisher
...
class FahrzeugContainer
{
    public Auto Auto { get; set; }

    public Motorrad Motorrad { get; set; }

    // Hier können dann weitere Properties stehen für deine Ableitungen....
}

Dann kannst du die Liste einfach per XML Serialisieren lassen.
Um die Daten wieder rauszulesen kannst du mit einer Null Prüfung die Properties pro Eintrag auslesen.

Eine Serialisierung der Ursprungsliste würde sonst deine zusätzlichen Properties bei den abgeleiteten Klassen verlieren, da du beim serialisieren den Grundtypen Fahrzeug angeben musst.
Entsprechend wäre nur eine Seralisierung eines Containers sinnvoll.

Aber die Frage ist,welchen Zweck du damit erfüllen willst.
Wenn es ein Export ist, wäre dies okay.
Wenn du die Daten aber als Datenbank speichern willst, nimm eine Lösung wie Sqlite etc.
Je nachdem welche Anforderungen du hast.

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.

ByteDevil Themenstarter:in
132 Beiträge seit 2013
vor 6 Jahren

Hallo T-Virus,

danke für deine Antwort.

Also mit meinem Beispiel meinte ich das auch mehrere Motorräder bzw Autos in der Liste sein können. Hätte ich vielleicht im Beispiel einbauen sollen.
Somit verstehe ich nicht ganz wieso deine Containerklasse ein Objekt von jedem zurückgeben. Meinst du damit ich könnte mir einfach einen Array mit den Objekten von jedem Typen in der Liste ausspucken lassen und das dann einzeln speichern?

Also ich habe mir eine Art Netzwerk Job Scheduler gebastelt, der bei bestimmten Strings die ihm gesendet werden unterschiedliche Dinge tun soll. Diese Jobs habe ich eben auf die oben gezeigte Weise implementiert. Nun habe ich eine Liste (ObservableCollection) mit diesen Jobs und wenn ich mein Programm wieder starte, möchte ich diese Liste natürlich wieder vorfinden. Dafür suche ich jetzt noch eine elegante Art und Weise.

T
2.224 Beiträge seit 2008
vor 6 Jahren

@ByteDevil
Um welche Liste geht es den?
Hast du dort eine Job Liste?
Es wäre hilfreicher, wenn du die genauen Typen und keine Beispiele zeigen könntest.

Mein Ansatz wäre mit meinem Beispiel oben, dass ich ein Objekt FahrzeugContainer anlege.
Dort setze ich dann pro Fahrzeug Typen die entsprechende Property und packe das Objekt in eine List<FahrzeugContainer>
Diese Liste müsste dann nur per XML serailisiert werden und der XML String in eine Datei geschrieben werden.
Wäre keine große Magie.

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.

2.079 Beiträge seit 2012
vor 6 Jahren

Probier doch einfach mal aus, was der XmlSerializer draus macht?
Bei dem kannst Du bekannte Datentypen zusätzlich angeben.
Soweit ich mich erinnere, kommt der mit sowas gut klar.
Du müsstest bloß noch einen parameterlosen Konstruktor ergänzen.

Je nach Art und Umfang der Daten würde ich aber auch zu einer Datenbank tendieren.
Das Entity Framework kennt drei verschiedene Varianten, mit Vererbung umzugehen:
Entity Framework Tutorial: nheritance Strategy in Code-First

EF Core kann dagegen (leider) nur TPH.
Das hängt glaube ich mit der Performance zusammen, die besonders bei TPT nicht die beste sein soll.

Welche Datenbank dahinter steht, ist ja erst mal egal.
Ich würde aber LocalDb verwenden, oder wenn es was Größeres wird, gleich der SqlServer.
Da kann man denke ich davon ausgehen, dass es am besten mit dem EF klar kommt.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

T
2.224 Beiträge seit 2008
vor 6 Jahren

Würde eher zu Sqlite tendieren, aber hängt eben davon ab welche Daten hier gespeichert und geladen werden sollen.
Ggf. wäre eine Datei die bessere Lösung, kommt aber eben auf die Daten sowie die Menge an.

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.

ByteDevil Themenstarter:in
132 Beiträge seit 2013
vor 6 Jahren

Das mit dem XmlSerializer klingt gar nicht übel. Werde das mal ausprobieren.
Hier mal der gesamte Code:


using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace NetworkJobScheduler
{
    class NetworkJobCollection : ObservableCollection<NetworkJob> { }

    #region base
    public abstract class NetworkJob : INotifyPropertyChanged
    {
        private TimeSpan delay;
        public TimeSpan Delay
        {
            get { return delay; }
            set { if (delay != value) { delay = value; OnPropertyChanged("Delay"); } }
        }

        private string message;
        public string Message
        {
            get { return message; }
            set { if (message != value) { if (value != string.Empty) message = value; OnPropertyChanged("Message"); } }
        }

        public abstract string Details { get; }
        public string Description { get { return ToString(); } }
        public string DelayString { get { return Delay == TimeSpan.Zero ? "" : Delay.ToString("hh\\:mm\\:ss"); } }

        public event PropertyChangedEventHandler PropertyChanged;

        public virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public void Execute()
        {
            Thread thread = new Thread(Subroutine);
            thread.Start();
        }

        protected abstract void Subroutine();

        public NetworkJob(string msg, TimeSpan? delay) { Message = msg; Delay = delay ?? TimeSpan.Zero; }
    }
    #endregion

    #region Reboot, Shutdown ...
    public class ShutDownNetworkJob : NetworkJob
    {
        public ShutDownNetworkJob(string msg, TimeSpan? delay = null) : base(msg, delay) { }

        public override string Details => "";

        public override string ToString() { return Delay == TimeSpan.Zero ? "Shuts down the computer immediately." : "Shuts down the computer after a while."; }

        protected override void Subroutine()
        {
            Thread.Sleep(Delay);
            var psi = new ProcessStartInfo("shutdown", "/s /t 0")
            {
                CreateNoWindow = true,
                UseShellExecute = false
            };
            Process.Start(psi);
        }
    }

    public class RebootNetworkJob : NetworkJob
    {
        public RebootNetworkJob(string msg, TimeSpan? delay = null) : base(msg, delay) { }

        public override string Details => "";

        protected override void Subroutine()
        {
            Thread.Sleep(Delay);
            var psi = new ProcessStartInfo("shutdown", "/r /t 0")
            {
                CreateNoWindow = true,
                UseShellExecute = false
            };
            Process.Start(psi);
        }

        public override string ToString() { return Delay == TimeSpan.Zero ? "Reboots the computer immediately." : "Reboots the computer after a while."; }
    }
    #endregion

    #region Execute programs, open URL's ...
    public class ExecuteFileNetworkJob : NetworkJob
    {
        private ProcessStartInfo startInfo;
        public ProcessStartInfo StartInfo
        {
            get { return startInfo; }
            set { if (startInfo != value) { startInfo = value; OnPropertyChanged("Details"); } }
        }

        public override string Details => StartInfo.FileName + " " + StartInfo.Arguments;

        public ExecuteFileNetworkJob(string msg, ProcessStartInfo startInfo, TimeSpan? delay = null) : base(msg, delay) { StartInfo = startInfo; }

        protected override void Subroutine()
        {
            Thread.Sleep(Delay);
            Process.Start(StartInfo);
        }

        public override string ToString() { return Delay == TimeSpan.Zero ? "Executes " + Path.GetFileName(startInfo.FileName) : "Executes " + Path.GetFileName(startInfo.FileName) + " after a while"; }
    }
    #endregion
}

Vielleicht möchtet ihr ja im allgemeinen mal drüber schauen. Ein Objekt der NetworkJobCollection ganz oben möchte ich halt speichern.

16.835 Beiträge seit 2008
vor 6 Jahren

Das sind eher Business Modelle und keine Entitäten.
Und speichern sollte man eben Entitäten.

Die Business Logik sollte zwar wissen, dass ein Execute in einem Thread laufen kann.
Aber ich halte es für ungünstig, dass es das Business Modell selbst tut und nicht die Business Logik drum herum.

PS: hier sieht die Verwendung von async/await von Tasks auch deutlich geeigneter aus als das manuelle Managen von Threads.

ByteDevil Themenstarter:in
132 Beiträge seit 2013
vor 6 Jahren

Was ist denn der Unterschied zwischen einem Businessmodell und einer Entity?

Ich habe es jetzt mit einem XmlSerializer implementiert und es sieht ganz gut aus. Das hier hat er mir ausgespuckt:


<?xml version="1.0"?>
<Jobs xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <JobArray>
    <JobObjekt xsi:type="ExecuteFileNetworkJob">
      <Delay>100000000</Delay>
      <Message>Browser</Message>
      <FileName>C:\Program Files (x86)\Mozilla Firefox\firefox.exe</FileName>
      <Arguments>-a -b</Arguments>
    </JobObjekt>
    <JobObjekt xsi:type="ShutDownNetworkJob">
      <Delay>200000000</Delay>
      <Message>Shutdown</Message>
    </JobObjekt>
    <JobObjekt xsi:type="RebootNetworkJob">
      <Delay>300000000</Delay>
      <Message>Reboot</Message>
    </JobObjekt>
    <JobObjekt xsi:type="OpenURLNetworkJob">
      <Delay>0</Delay>
      <Message>Website</Message>
      <URL>www.mycsharp.de</URL>
    </JobObjekt>
  </JobArray>
</Jobs>

Gab nur drei Dinge zu beachten: Ich musste in den Ableitungen jeweils einen Standardkonstruktor implementieren, ein TimeSpan lässt sich nicht ohne weiteres serialisieren und ein Objekt vom Typ ProcessStartInfo ebenfalls nicht. ProcessStartInfo hat ja lediglich 2 strings gekapselt welche jeweils eine eigene Property bekamen und der TimeSpan wurde einfach in seine Ticks zerlegt und so gespeichert.

Ich danke euch für den Schups in die richtige Richtung 😃

Viele Grüße,
ByteDevil

16.835 Beiträge seit 2008
vor 6 Jahren

Ein Business Modell ist teil der Logikschickt und muss nicht zwangsläufig serialisierbar oder speicherbar abgebildet sein.
Eine Enität ist teil der Datenschicht und muss so abgebildet sein, dass man es aber speichern kann.

[Artikel] Drei-Schichten-Architektur

ByteDevil Themenstarter:in
132 Beiträge seit 2013
vor 6 Jahren

Danke für den Link, Abt. Werde es mir morgen mal zu Gemüte führen.

2.298 Beiträge seit 2010
vor 6 Jahren

Hallo ByteDevil,

aufbauend auf dem bisher beschriebenen empfehle ich dir noch eine Konfigurationsklasse für deine Jobs zu erstellen. - Von dieser erben dann spezifische Job-Konfigurationsklassen.

Speichern und Laden würdest du dann nur die Konfigurationen und entsprechend deine Jobs mit den Konfigurationen füttern.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |