Announcement

Collapse
No announcement yet.

Serialisierungsproblem in C#

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Serialisierungsproblem in C#

    Hallo,

    ich habe ein Problem bei der Verwendung einer Collection, bei der Duplikate ausgeschlossen werden sollen und die (de)serialisiert werden soll. Ich versuche das Ganze mal Step by Step zu erklären, ich hoffe es wird nicht zu unverständlich...

    Also, ich brauche zunächst eine Liste, die ausschließlich Zeichenketten aufnehmen kann. Ich habe mich hier für die Vererbung von Collection(T) entschieden:

    Code:
    [Serializable()]
    public class StringList : Collection<string> {
      public override string ToString() {
        string[] array = new String[Count];
        CopyTo(array, 0);
        return "[" + String.Join(",",array) + "]";
      }
    }
    Die Klasse implementiert noch einige weitere Methoden, diese sind aber erstmal nicht weiter relevant.

    Nun fülle ich die Liste und serialisiere sie in eine externe Datei:

    Code:
    public static void Main() {
      StringList sl = new StringList();
      sl.Add("rot");
      sl.Add("gelb");
      sl.Add("gruen");
      sl.Add("gruen");
      Console.WriteLine(sl);
      SerializeObject(sl);
    }
    
    private static void SerializeObject(object obj) {
      try {
        FileStream stream = new FileStream("Ampel.dat", FileMode.Create);
        try {
          BinaryFormatter formatter = new BinaryFormatter();
          formatter.Serialize(stream, obj);
        }
        finally {
          stream.Close();
        }
      }
      catch { }
    }
    Und da ich den Dateiinhalt bei einer späteren Programmnutzung wieder verarbeiten möchte, brauche ich natürlich noch eine Methode zur Deserialisierung:

    Code:
    public static void Main() {
      object obj = DeserializeObject();
      if (obj != null) {
        StringList sl = (StringList)obj;
        Console.WriteLine(sl);
      }
      else {
        Console.WriteLine("Fehler");
      }
    }
    
    private static object DeserializeObject() {
      try {
        FileStream stream = new FileStream("Ampel.dat", FileMode.Open);
        try {
          BinaryFormatter formatter = new BinaryFormatter();
          object obj = formatter.Deserialize(stream);
          return obj;
        }
        finally {
          stream.Close();
        }
      }
      catch {
        return null;
      }
    }
    Soweit so gut. Das Einlesen und Rausschreiben der Daten funktioniert genauso wie ich es mir vorstelle.
    Das Problem ist nun, dass ich die Liste gern frei von Duplikaten halten möchte. Dazu habe ich eine neue Add-Methode in meiner StringList-Klasse implementiert:

    Code:
    [Serializable()]
    public class StringList : Collection<string> {
      public new void Add(string item) {
        if (!Contains(item)) base.Add(item);
      }
    }
    Wenn ich nun die einzelnen Zeichenketten sozusagen "manuell" hinzufüge, funktioniert das natürlich einwandfrei.
    Bei der Deserialisierung wird die Add-Methode dann jedoch nie aufrufen, da ja hier ein Cast stattfindet.

    Und jetzt zu meiner Frage: Lassen sich die Duplikate trotzdem irgendwie filtern?
    Mir ist jetzt so auf Anhieb keine clevere Lösung eingefallen, außer die Liste nach dem Einlesen zu durchlaufen und die gleichen Einträge per Hand zu entfernen, was aber bei einer relativ umfangreichen Liste nicht sehr effizient ist. Dürfte ein quadratisches Laufzeitverhalten sein.

    Vielen Dank schonmal und viele Grüße

  • #2
    Es dürfte sich doch um einmaliges Problem handeln, so dass du die Liste einliest, der Anwender ergänzte diese (Methode "add") und du schreibst sie weg. Sie kommt also erstmalig wohl mit Duplikaten an. Also wäre es sinnvoll die Liste vor der Verarbeitung mit deinem Programm zu bereinigen. Alternativ ist halt nicht die Serialisierung zu nutzen, sondern eben die Daten einfach lesen, der Liste hinzufügen und dabei nur die aufzunehmen, wo keine Duplikate vorliegen http://msdn.microsoft.com/de-de/libr.../s2tte0y1.aspx
    Christian

    Comment


    • #3
      Hallo Christian,
      ich hab deinen Rat befolgt und mir nochmal ein paar andere Konzepte angeschaut. Ich habe mich jetzt für den XmlSerializer entschieden. Interessanterweise ruft der bei der Deserialisierung die Add-Methode auf, wenn der Typ einer Objekteigenschaft eine Collection ist. Damit musste ich nur noch eine Art Wrapper um meine StringList bauen (für das XML-Root-Element) und jetzt funktioniert das absolut einwandfrei.
      Viele Grüße

      Comment


      • #4
        Ich würde vermutlich das Lesen der Daten und das Entfernen von Duplikaten in zwei logisch getrennte Einheiten packen (separation of concerns). Baue erstmal eine Möglichkeit um Sachen aus der Datei zu lesen, die Einträge können in dem eingelesenen Objekt auch öfters vorkommen. Danach packst Du die Objekte einfach und packst sie in eine HashSet<String>. String implementiert sowieso wunderbar GetHashcode(), danach ist jeder String nur noch einmal im HashSet vorhanden.
        Gefühlt macht man meistens etwas falsch wenn man von Collections oder Listen ableitet. Das ist jetzt allerdings Code Design und letzten Endes auch eine Geschmacksfrage.

        Comment


        • #5
          Hallo fanderlf,

          prinzipiell gebe ich dir vollkommen recht, an das HashSet hatte ich auch schon gedacht. Allerdings kann es sein, dass ich in Zukunft die Filterung nicht nur nach Duplikaten vornehmen möchte, sondern auch allle Zeichenketten ignorieren will, die beispielsweise mit einem bestimmten regulären Ausdruck matchen und da wäre ein HashSet etwas haarig. Deshalb hatte ich mich für die Collection entschieden. Die Implementierung ist auch gar nicht so schwer, man muss ja einfch nur vererben und anschließend die Methoden überschreiben/ersetzen, die vom Standardverhalten abweichen. Funktioniert sehr gut.
          Bei der Trennung von Verantwortlichkeiten hast du ebenfalls recht. In diesem Fall hieße das aber, zwei getrennte Schritte zu programmieren, was wie bereits erwähnt, die Laufzeit erhöht. Aus diesem Grund wollte ich gern die Duplikate gleich beim Einlesen entfernen, um die Liste nicht zweimal verarbeiten zu müssen.

          Viele Grüße

          Comment


          • #6
            Also für mich klingt das trotzdem danach dass das nichts in der Liste verloren hat. Was spricht gegen eine kleine Klasse Die Deine Daten einfach so filtert wie Du sie brauchst und dann ein IEnumerable<string> zurück gibt.

            Etwa so:

            [highlight=c#]
            public class InputFilter
            {
            public IEnumerable<string> Reduce(IEnumerable<string> input)
            {
            return input.Distinct().Where(x => x.StartsWith("[START]");
            }
            }
            [/highlight]

            Das wäre eine Beispielimplementierung die Du sogar sehr leicht testen kannst.

            Dieses Vorgehen würde ich auf jeden Fall empfehlen. Meines Erachtens hat eine Liste die Aufgabe eine Liste zu sein, sprich am Ende soll sie sich darum kümmern, dass Einträge hinzugefügt und weggenommen werden können. Das HashSet erachte ich tatsächlich als einen plausiblen Spezialfall weil es für Performanz optimiert ist. Dazu kommt jetzt noch wieviele Einträge sich später in der Liste befinden werden, sprich ob Performanz an dieser Stelle überhaupt wichtig ist. Wirst Du in Zukunft sehr viele Einträge haben würde ich dringend zur Verwendung eines HashSets raten, weil die Einträge in einem HashSet wesentlich schneller gefunden als in einer Liste. Bedenke dass jedesmal wenn Du ein neues Item hinzufügen willst die komplette Liste durchgegangen werden muss um zu sehen ob sich dieses Item schon in der Liste befindet.

            Comment


            • #7
              Warum nicht eine Klasse von der gewünschten Liste ableiten und Methoden überschreiben / hinzufügen. Das würde der OOP entsprechen
              Christian

              Comment


              • #8
                In meiner Implementierung bin ich von einem ganz ganz schmalen Interface abhängig (IEnumerable<string>). Weniger spezifisch geht schon fast nicht mehr. Bei Vererbung über eine Liste bin ich an die komplette Implementierung Liste gekoppelt. Lehrt man ausserdem nicht überall "Composition over Inheritance"?

                Dass ich Vererbung an sich nicht mag ist rein persönlicher Geschmack. Die Diskussion dazu hatten wir schon mal und gehört nicht hier hin
                Zuletzt editiert von fanderlf; 15.07.2013, 11:51.

                Comment


                • #9
                  Wieso können da überhaupt Duplikate drin sein? Wäre es nicht am sinnvollsten beim erzeugen des binär Formats nur sinnvolles reinzuschreiben und nicht nachher unsinniges auszusortieren?
                  Wenn du das Problem hast das sich der Inhalt auch über den Array Indexer ändert und nicht nur über die Add Methode dann überschreibe halt den Indexer auch und prüfe dort auf Duplikate.


                  Lehrt man ausserdem nicht überall "Composition over Inheritance"?
                  Leider Nein. Schreib noch ein this an den Parameter dann kann man sich aussuchen wie man das aufrufen will.

                  Comment


                  • #10
                    Originally posted by Ralf Jansen View Post
                    Wieso können da überhaupt Duplikate drin sein? Wäre es nicht am sinnvollsten beim erzeugen des binär Formats nur sinnvolles reinzuschreiben und nicht nachher unsinniges auszusortieren?
                    Wenn du das Problem hast das sich der Inhalt auch über den Array Indexer ändert und nicht nur über die Add Methode dann überschreibe halt den Indexer auch und prüfe dort auf Duplikate.




                    Leider Nein. Schreib noch ein this an den Parameter dann kann man sich aussuchen wie man das aufrufen will.
                    Zu ersterem: Es kommt halt drauf an ob er die Quelle der Datei unter Kontrolle hat oder nicht. Wenn die aus einem externen System kommt kann er nix gegen Duplikate machen. Wenn er selber serialisiert würde ich auch doppelte gar nicht erst reinschreiben.

                    Zu zweiterem: Ich weiss nicht ob ich so etwas als Extension Methode haben möchte. Im Prinzip stünde das dann auf alle globalen IEnumerable<string> zur Verfügung. Je nach Anwendungsfall halte ich das aber für Namespace Verschmutzung. Wenn man es oft braucht spricht natürlich nichts dagegen. Die Frage ist ob man überhaupt eine separate Methode braucht wenn man aktuell sowieso nur .Distinct() aufruft

                    Comment


                    • #11
                      Zu ersterem: Es kommt halt drauf an ob er die Quelle der Datei unter Kontrolle hat oder nicht. Wenn die aus einem externen System kommt kann er nix gegen Duplikate machen.
                      Wie sollte er beim BinaryFormatter die Quelle nicht im Griff haben? Es kann jemand anderes die gleiche Klasse nicht anders implementieren. Das würde sofort knallen.

                      Comment


                      • #12
                        Habe das auch so aufgefasst, dass er die Daten - zumidnestens erstmalig - von extern bekommt. Sonst wäre die Frage hier völlig sinnlos. -> doppelte reinschreiben
                        Christian

                        Comment


                        • #13
                          Also während mein Programm ausgeführt wird, kann ich natürlich sämtliche neue Elemente vor der Einfügung prüfen, um somit Duplikate gleich auszuschließen. Das heißt, bei der Serialisierung ist meine Liste "sauber".
                          Das Problem entsteht lediglich beim Programmstart, wenn die Datei zum ersten Mal eingelesen wird. Wie von fanderlf und Christian korrekt angemerkt, ist die Nutzung der Quelle problematisch, da die Datei in meinem Anwendungsfall auch von einem anderen Programm verarbeitet werden kann und die beiden Programme sich funktional unterscheiden. Aus diesem Grund können durchaus Duplikate auftreten und diese müssen gefiltert werden.

                          Originally posted by Ralf Jansen View Post
                          Wie sollte er beim BinaryFormatter die Quelle nicht im Griff haben? Es kann jemand anderes die gleiche Klasse nicht anders implementieren. Das würde sofort knallen.
                          Das ist ja so leider nicht ganz korrekt, denn man kann einmal eine Klasse zur Serialisierung markieren und diese dann in mehreren (verschiedenen) Programmen wiederverwenden. Nimm mal an, du definierst einen Namespace MyData.IO und legst dort die Klassen zur (De)Serialisierung ab. Anschließend bindest du den Namespace in deine Programme ein. Der BinaryFormatter würde keine Exception auslösen, da sich die Klasse selbst (und damit der strukturelle Aufbau der Datei) ja nie unterscheidet.

                          Comment


                          • #14
                            Ich würde Dir dringendst davon abraten in mehreren Programmen denselben Binary Formatter zu verwenden. Sollte sich das Format ändern musst Du in einem Zug alle solchen Files und Programme die sich noch im Umlauf befinden gleichzeitig updaten, sonst kannst Du die alten Dateien nie wieder aufmachen oder hast nur noch Datenmüll. In diesem Szenario würde ich dann entweder auf XML oder etwas Platzsparender JSON setzen. Wenn Dir die Dateien dann noch zu groß sind kannst Du sie noch komprimieren (zip, rar o.ä.).

                            Comment


                            • #15
                              Jepp, bin ja schon auf den XmlSerializer umgestiegen (siehe vorheriges Posting). Ich wollte nur nochmal auf die Frage zur Entstehung der Duplikate antworten.

                              Comment

                              Working...
                              X