Suche…


Bemerkungen

Das binäre Serialisierungsmodul ist Teil des .NET-Frameworks, die hier aufgeführten Beispiele sind jedoch spezifisch für C #. Im Vergleich zu anderen im .NET-Framework integrierten Serialisierungs-Engines ist der binäre Serialisierer schnell und effizient und erfordert in der Regel sehr wenig zusätzlichen Code, um ihn zu aktivieren. Es ist jedoch auch weniger tolerant gegenüber Codeänderungen. Das heißt, wenn Sie ein Objekt serialisieren und dann eine geringfügige Änderung an der Definition des Objekts vornehmen, wird es wahrscheinlich nicht korrekt deserialisiert.

Ein Objekt serialisierbar machen

Fügen Sie das Attribut [Serializable] , um ein gesamtes Objekt für die binäre Serialisierung zu markieren:

[Serializable]
public class Vector
{
    public int X;
    public int Y;
    public int Z;

    [NonSerialized]
    public decimal DontSerializeThis;

    [OptionalField]
    public string Name;
}

Alle Member werden serialisiert, es sei denn, wir verwenden das Attribut [NonSerialized] explizit. In unserem Beispiel sind X , Y , Z und Name alle serialisiert.

Alle Mitglieder müssen bei der Deserialisierung anwesend sein, sofern sie nicht mit [NonSerialized] oder [OptionalField] . In unserem Beispiel sind X , Y und Z erforderlich, und die Deserialisierung schlägt fehl, wenn sie nicht im Stream vorhanden sind. DontSerializeThis wird immer auf default(decimal) (0) gesetzt. Wenn Name im Stream vorhanden ist, wird er auf diesen Wert gesetzt, andernfalls wird er auf default(string) (was null ist) gesetzt. Der Zweck von [OptionalField] besteht darin, ein wenig [OptionalField] bereitzustellen.

Serialisierungsverhalten mit Attributen steuern

Wenn Sie das Attribut [NonSerialized] , hat dieses Mitglied nach der Deserialisierung immer seinen Standardwert (z. B. 0 für ein int , null für string , false für ein bool usw.), unabhängig von der Initialisierung im Objekt selbst (Konstruktoren, Deklarationen usw.). Um dies zu kompensieren, werden die Attribute [OnDeserializing] (kurz BEFORE Deserializing) und [OnDeserialized] (kurz AFTER Deserializing) zusammen mit ihren Gegenstücken [OnSerializing] und [OnSerialized] bereitgestellt.

Angenommen, wir möchten unserem Vektor eine "Bewertung" hinzufügen und sicherstellen, dass der Wert immer bei 1 beginnt. Die Art und Weise, wie sie unten geschrieben ist, wird nach der Deserialisierung 0 sein:

[Serializable]
public class Vector
{
    public int X;
    public int Y;
    public int Z;

    [NonSerialized]
    public decimal Rating = 1M;

    public Vector()
    {
        Rating = 1M;
    }

    public Vector(decimal initialRating)
    {
        Rating = initialRating;
    }
}

Um dieses Problem zu beheben, können Sie einfach die folgende Methode innerhalb der Klasse hinzufügen, um sie auf 1 zu setzen:

[OnDeserializing]
void OnDeserializing(StreamingContext context)
{
    Rating = 1M;
}

Oder wenn wir einen berechneten Wert festlegen möchten, können wir warten, bis die Deserialisierung abgeschlossen ist, und dann festlegen:

[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
    Rating = 1 + ((X+Y+Z)/3);
}

Ebenso können Sie mit [OnSerializing] und [OnSerialized] steuern, wie Dinge ausgeschrieben werden.

Mehr Kontrolle durch Implementierung von ISerializable

Dies würde mehr Kontrolle über die Serialisierung, das Speichern und Laden von Typen erhalten

Implementieren Sie die ISerializable-Schnittstelle und erstellen Sie einen leeren Konstruktor zum Kompilieren

[Serializable]
public class Item : ISerializable
{
    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public Item ()
    {

    }

    protected Item (SerializationInfo info, StreamingContext context)
    {
        _name = (string)info.GetValue("_name", typeof(string));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("_name", _name, typeof(string));
    }
}

Für die Datenserialisierung können Sie den gewünschten Namen und den gewünschten Typ angeben

info.AddValue("_name", _name, typeof(string));

Wenn die Daten deserialisiert sind, können Sie den gewünschten Typ lesen

_name = (string)info.GetValue("_name", typeof(string));

Serialisierungssurrogate (Implementieren von ISerializationSurrogate)

Implementiert einen Serialisierungssurrogat-Selektor, mit dem ein Objekt die Serialisierung und Deserialisierung eines anderen Objekts durchführen kann

Außerdem kann eine Klasse, die selbst nicht serialisierbar ist, ordnungsgemäß serialisiert oder deserialisiert werden

Implementieren Sie die ISerializationSurrogate-Schnittstelle

public class ItemSurrogate : ISerializationSurrogate
{
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        var item = (Item)obj;
        info.AddValue("_name", item.Name);
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        var item = (Item)obj;
        item.Name = (string)info.GetValue("_name", typeof(string));
        return item;
    }
}

Dann müssen Sie Ihren IFormatter über die Surrogate informieren, indem Sie einen SurrogateSelector definieren, initialisieren und Ihrem IFormatter zuweisen

var surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());    
var binaryFormatter = new BinaryFormatter
{
    SurrogateSelector = surrogateSelector
};

Auch wenn die Klasse nicht als serialisierbar gekennzeichnet ist.

//this class is not serializable
public class Item
{
    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

Die komplette Lösung

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace BinarySerializationExample
{
    class Item
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    }

    class ItemSurrogate : ISerializationSurrogate
    {
        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
        {
            var item = (Item)obj;
            info.AddValue("_name", item.Name);
        }

        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            var item = (Item)obj;
            item.Name = (string)info.GetValue("_name", typeof(string));
            return item;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var item = new Item
            {
                Name = "Orange"
            };

            var bytes = SerializeData(item);
            var deserializedData = (Item)DeserializeData(bytes);
        }

        private static byte[] SerializeData(object obj)
        {
            var surrogateSelector = new SurrogateSelector();
            surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());

            var binaryFormatter = new BinaryFormatter
            {
                SurrogateSelector = surrogateSelector
            };

            using (var memoryStream = new MemoryStream())
            {
                binaryFormatter.Serialize(memoryStream, obj);
                return memoryStream.ToArray();
            }
        }

        private static object DeserializeData(byte[] bytes)
        {
            var surrogateSelector = new SurrogateSelector();
            surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());

            var binaryFormatter = new BinaryFormatter
            {
                SurrogateSelector = surrogateSelector
            };

            using (var memoryStream = new MemoryStream(bytes))
                return binaryFormatter.Deserialize(memoryStream);
        }
    }
}

Serialisierungsbinder

In der Mappe können Sie überprüfen, welche Typen in Ihre Anwendungsdomäne geladen werden

Erstellen Sie eine von SerializationBinder geerbte Klasse

class MyBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        if (typeName.Equals("BinarySerializationExample.Item"))
            return typeof(Item);
        return null;
    }
}

Jetzt können wir prüfen, welche Typen geladen werden und auf dieser Basis entscheiden, was wir wirklich erhalten möchten

Damit Sie eine Sammelmappe verwenden können, müssen Sie sie dem BinaryFormatter hinzufügen.

object DeserializeData(byte[] bytes)
{
    var binaryFormatter = new BinaryFormatter();
    binaryFormatter.Binder = new MyBinder();

    using (var memoryStream = new MemoryStream(bytes))
        return binaryFormatter.Deserialize(memoryStream);
}

Die komplette Lösung

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace BinarySerializationExample
{
    class MyBinder : SerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            if (typeName.Equals("BinarySerializationExample.Item"))
                return typeof(Item);
            return null;
        }
    }

    [Serializable]
    public class Item
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var item = new Item
            {
                Name = "Orange"
            };

            var bytes = SerializeData(item);    
            var deserializedData = (Item)DeserializeData(bytes);
        }

        private static byte[] SerializeData(object obj)
        {
            var binaryFormatter = new BinaryFormatter();
            using (var memoryStream = new MemoryStream())
            {
                binaryFormatter.Serialize(memoryStream, obj);
                return memoryStream.ToArray();
            }
        }

        private static object DeserializeData(byte[] bytes)
        {
            var binaryFormatter = new BinaryFormatter
            {
                Binder = new MyBinder()
            };

            using (var memoryStream = new MemoryStream(bytes))
                return binaryFormatter.Deserialize(memoryStream);
        }
    }
}

Einige gotchas in Rückwärtskompatibilität

Dieses kleine Beispiel zeigt, wie Sie die Abwärtskompatibilität in Ihren Programmen verlieren können, wenn Sie sich vorher nicht darum kümmern. Und Möglichkeiten, den Serialisierungsprozess besser zu kontrollieren

Zuerst schreiben wir ein Beispiel für die erste Version des Programms:

Version 1

[Serializable]
class Data
{
    [OptionalField]
    private int _version;
    
    public int Version
    {
        get { return _version; }
        set { _version = value; }
    }
}

Und jetzt nehmen wir an, dass in der zweiten Version des Programms eine neue Klasse hinzugefügt wurde. Und wir müssen es in einem Array speichern.

Nun sieht der Code so aus:

Version 2

[Serializable]
class NewItem
{
    [OptionalField]
    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

[Serializable]
class Data
{
    [OptionalField]
    private int _version;

    public int Version
    {
        get { return _version; }
        set { _version = value; }
    }

    [OptionalField]
    private List<NewItem> _newItems;

    public List<NewItem> NewItems
    {
        get { return _newItems; }
        set { _newItems = value; }
    }
}

Und Code zum Serialisieren und Deserialisieren

private static byte[] SerializeData(object obj)
{
    var binaryFormatter = new BinaryFormatter();
    using (var memoryStream = new MemoryStream())
    {
        binaryFormatter.Serialize(memoryStream, obj);
        return memoryStream.ToArray();
    }
}

private static object DeserializeData(byte[] bytes)
{
    var binaryFormatter = new BinaryFormatter();
    using (var memoryStream = new MemoryStream(bytes))
        return binaryFormatter.Deserialize(memoryStream);
}

Was passiert also, wenn Sie die Daten im Programm von v2 serialisieren und versuchen, sie im Programm von v1 zu deserialisieren?

Sie erhalten eine Ausnahme:

System.Runtime.Serialization.SerializationException was unhandled
Message=The ObjectManager found an invalid number of fixups. This usually indicates a problem in the Formatter.Source=mscorlib
StackTrace:
   at System.Runtime.Serialization.ObjectManager.DoFixups()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
   at Microsoft.Samples.TestV1.Main(String[] args) in c:\Users\andrew\Documents\Visual Studio 2013\Projects\vts\CS\V1 Application\TestV1Part2\TestV1Part2.cs:line 29
   at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

Warum?

Der ObjectManager verfügt über eine andere Logik zum Auflösen von Abhängigkeiten für Arrays sowie für Referenz- und Werttypen. Wir haben eine Reihe neuer Referenztypen hinzugefügt, die in unserer Baugruppe fehlen.

Wenn ObjectManager versucht, Abhängigkeiten aufzulösen, wird das Diagramm erstellt. Wenn das Array erkannt wird, kann es nicht sofort repariert werden, sodass eine Dummy-Referenz erstellt und das Array später korrigiert wird.

Und da dieser Typ nicht in der Baugruppe ist und Abhängigkeiten nicht behoben werden können. Aus irgendeinem Grund wird das Array nicht aus der Liste der Elemente für die Fixes entfernt. Am Ende wird eine Ausnahme "IncorrectNumberOfFixups" ausgelöst.

Es handelt sich um einige "Gotchas" im Prozess der Serialisierung. Aus irgendeinem Grund funktioniert es nicht korrekt nur für Arrays mit neuen Referenztypen.

A Note:
Similar code will work correctly if you do not use arrays with new classes

Und der erste Weg, um das Problem zu beheben und die Kompatibilität aufrechtzuerhalten?

  • Verwenden Sie anstelle von Klassen eine Sammlung neuer Strukturen oder verwenden Sie ein Wörterbuch (mögliche Klassen), da es sich bei einem Wörterbuch um eine Sammlung von keyvaluepair (dessen Struktur) handelt.
  • Verwenden Sie ISerializable, wenn Sie den alten Code nicht ändern können


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow