Objekte mit Events serialisieren mit dem BinaryFormatter

In meinem aktuellen Projekt muss ein recht komplexer Objektbaum in eine Datei serialisiert werden, um eine Speichern und Laden Funktion bieten zu können. Da es im Modell auch zirkuläre Referenzen gibt und ich im vorliegenden Fall nichts von SOAP halte, verwende ich den BinaryFormatter für diese Aufgabe (obwohl das von mir beschriebene Problem ziemlich sicher auch auf andere Formatter zutreffen wird).

Aber nun zum eigentlichen Problem: das alle Klassen die serialisiert werden sollen das Attribut Serializable benötigen sollte allen klar sein. Was ich jedoch schmerzhaft feststellen musste ist, dass auch jede Klasse die sich für ein Event einer zu serialisierenden Klasse registriert serialisierbar sein muss. Denn die Events werden mit serialisiert, sodass sie nach der Deserialisierung weiter verknüpft bleiben. Bei den Kollegen von Sanity Free Coding wird das ganze auch noch ausführlicher erläutert und auch ein Lösungsvorschlag gegeben.

Der dort beschriebene Ansatz birgt allerdings auch ein paar Stolper-fallen: in dem Beispiel wird das Event nämlich nie mitserialisiert, also auch nicht, wenn die auf das Event lauschende Klasse serialisierbar ist. In meinem Projekt ist es so, dass der Objektbaum über Listen mit Parent Child Funktion verfügt. Darüber kann im Baum hoch und runter navigiert werden. Außerdem bieten die Listen Events, die bei Änderung daran gefeuert werden z.B. wenn ein neues Objekt hinzugefügt oder gelöscht wird. Manche Objekte im Baum müssen wissen, wenn sich was im Baum an anderer Stelle geändert hat, um sich selbst zu aktualisieren und den Baum somit konsistent zu halten. Daher registrieren sie sich für die Changed Events. Diese Events sollten also auch nach der Deserialisierung wieder hergestellt sein. Andererseits registrieren sich auch Oberflächensteuerelemente für die Changed Events, damit die Oberfläche dementsprechend aktualisiert werden kann. Diese Events werden beim Aufbau der Oberfläche aber ohnehin jedesmal neu verdrahtet, müssen also nicht serialisiert werden.

Eine Möglichkeit wäre nun, für jedes Objekt im Modell ISerializable und einen Deserialisierungskonstruktor zu implementieren und die komplette De-/Serialisierung selber zu machen. Dabei könnte man im Deserialisierungskonstruktor die Eventzuordnung ebenfalls jedesmal neu verdrahten. Was aber bei komplexen Objekten wenig Spass macht und bei Änderungen am Objekt jedesmal mit angepasst werden muss. Mein Ansatz ist eine Erweiterung der von Sanity Free Coding vorgeschlagenen Methode. Beim Registrieren der Events in der add sowie beim Deregistrieren in der remove Methode wird das Event Target per Reflection überprüft. Ist das Target serialisierbar, wird das Event einem privaten event zugeordnet, das serialisiert wird. Ist das Target nicht serialisierbar, wie z.B. unsere Oberflächensteuerelemente, wird das event Feld mit vorangestelltem [NonSerializable] verwendet. Genial einfach und einfach genial. Hier noch etwas Code um sich das besser vorstellen zu können:

[NonSerialized]
private EventHandler<ParentChildListChangedEventArgs<C>> changedNonSerializable;
private EventHandler<ParentChildListChangedEventArgs<C>> changedSerializable;

public event EventHandler<ParentChildListChangedEventArgs<C>> Changed
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    add 
    {
        if (value.Target.GetType().IsSerializable)
            changedSerializable += value;
        else
            changedNonSerializable += value;
    }
    [MethodImpl(MethodImplOptions.Synchronized)]
    remove 
    {
        if (value.Target.GetType().IsSerializable)
            changedSerializable -= value;
        else
            changedNonSerializable -= value;
    }
}

gefeuert wird das ganze dann wie folgt:

protected virtual void OnChanged(ParentChildListChangedEventArgs<C> e)
{
    if (changedNonSerializable != null) changedNonSerializable(this, e);
    if (changedSerializable != null) changedSerializable(this, e);
}

About this entry