Ricerca…


Osservazioni

Il motore di serializzazione binario fa parte del framework .NET, ma gli esempi qui riportati sono specifici di C #. Rispetto ad altri motori di serializzazione integrati nel framework .NET, il serializzatore binario è veloce ed efficiente e di solito richiede pochissimo codice aggiuntivo per farlo funzionare. Tuttavia, è anche meno tollerante alle modifiche al codice; cioè, se serializzi un oggetto e poi apporti una leggera modifica alla definizione dell'oggetto, probabilmente non lo deserializzi correttamente.

Rendere serializzabile un oggetto

Aggiungi l'attributo [Serializable] per contrassegnare un intero oggetto per la serializzazione binaria:

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

    [NonSerialized]
    public decimal DontSerializeThis;

    [OptionalField]
    public string Name;
}

Tutti i membri verranno serializzati a meno che non si [NonSerialized] esplicitamente utilizzando l'attributo [NonSerialized] . Nel nostro esempio, X , Y , Z e Name sono tutti serializzati.

Tutti i membri devono essere presenti alla deserializzazione a meno che non siano contrassegnati con [NonSerialized] o [OptionalField] [NonSerialized] [OptionalField] . Nel nostro esempio, X , Y e Z sono tutti richiesti e la deserializzazione fallirà se non sono presenti nello stream. DontSerializeThis sarà sempre impostato su default(decimal) (che è 0). Se Name è presente nello stream, verrà impostato su quel valore, altrimenti verrà impostato su default(string) (che è null). Lo scopo di [OptionalField] è di fornire un po 'di tolleranza della versione.

Controllo del comportamento di serializzazione con attributi

Se si utilizza l'attributo [NonSerialized] , quel membro avrà sempre il suo valore predefinito dopo la deserializzazione (ad esempio 0 per un int , null per string , false per un bool , ecc.), Indipendentemente dall'inizializzazione eseguita nell'oggetto stesso (costruttori, dichiarazioni, ecc.). Per compensare, vengono [OnDeserializing] gli attributi [OnDeserializing] (chiamato solo BEFORE deserializing) e [OnDeserialized] (chiamato solo AFTER deserializing) insieme alle loro controparti, [OnSerializing] e [OnSerialized] .

Supponiamo di voler aggiungere un "Rating" al nostro Vector e vogliamo assicurarci che il valore inizi sempre a 1. Il modo in cui è scritto qui sotto, sarà 0 dopo essere stato deserializzato:

[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;
    }
}

Per risolvere questo problema, possiamo semplicemente aggiungere il seguente metodo all'interno della classe per impostarlo su 1:

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

Oppure, se vogliamo impostarlo su un valore calcolato, possiamo aspettare che finisca la deserializzazione e quindi impostarlo:

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

Allo stesso modo, possiamo controllare come vengono scritte le cose usando [OnSerializing] e [OnSerialized] .

Aggiungere più controllo implementando ISerializable

Ciò otterrebbe un maggiore controllo sulla serializzazione, su come salvare e caricare i tipi

Implementare un'interfaccia ISerializable e creare un costruttore vuoto da compilare

[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));
    }
}

Per la serializzazione dei dati, è possibile specificare il nome desiderato e il tipo desiderato

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

Quando i dati sono deserializzati, sarai in grado di leggere il tipo desiderato

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

Surrogati di serializzazione (implementazione di ISerializationSurrogate)

Implementa un selettore surrogato di serializzazione che consente a un oggetto di eseguire la serializzazione e la deserializzazione di un altro

Inoltre consente di serializzare o deserializzare correttamente una classe che non è serializzabile

Implementare l'interfaccia ISerializationSurrogate

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;
    }
}

Quindi devi informare il tuo IFormatter dei surrogati definendo e inizializzando un SurrogateSelector e assegnandolo al tuo IFormatter

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

Anche se la classe non è marcata serializzabile.

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

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

La soluzione completa

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);
        }
    }
}

Serialization Binder

Il raccoglitore ti dà l'opportunità di verificare quali tipi vengono caricati nel dominio dell'applicazione

Creare una classe ereditata da SerializationBinder

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

Ora possiamo controllare quali tipi stanno caricando e su questa base per decidere cosa vogliamo veramente ricevere

Per usare un raccoglitore, devi aggiungerlo a BinaryFormatter.

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

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

La soluzione completa

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);
        }
    }
}

Alcuni trucchi in retrocompatibilità

Questo piccolo esempio mostra come si può perdere la retrocompatibilità nei propri programmi se non si presta attenzione a ciò. E i modi per ottenere un maggiore controllo del processo di serializzazione

Inizialmente, scriveremo un esempio della prima versione del programma:

Versione 1

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

E ora, supponiamo che nella seconda versione del programma sia stata aggiunta una nuova classe. E abbiamo bisogno di memorizzarlo in un array.

Ora il codice sarà simile a questo:

Versione 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; }
    }
}

E codice per serializzare e deserializzare

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);
}

E quindi, cosa succederebbe quando serializzaste i dati nel programma di v2 e cerchereste di deserializzarli nel programma di v1?

Ottieni un'eccezione:

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()

Perché?

L'ObjectManager ha una logica diversa per risolvere le dipendenze per gli array e per i tipi di riferimento e valore. Abbiamo aggiunto una serie di nuovi il tipo di riferimento che è assente nel nostro assembly.

Quando ObjectManager tenta di risolvere le dipendenze costruisce il grafico. Quando vede la matrice, non può risolverlo immediatamente, in modo da creare un riferimento fittizio e quindi correggere l'array in un secondo momento.

E poiché questo tipo non è nell'assembly e le dipendenze non possono essere riparate. Per qualche ragione, non rimuove la matrice dall'elenco di elementi per le correzioni e alla fine genera un'eccezione "IncorrectNumberOfFixups".

Sono alcuni "trucchi" nel processo di serializzazione. Per qualche ragione, non funziona correttamente solo per le matrici di nuovi tipi di riferimento.

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

E il primo modo per risolverlo e mantenere la compatibilità?

  • Usa una collezione di nuove strutture piuttosto che classi o usa un dizionario (possibili classi), perché un dizionario è una raccolta di keyvaluepair (è la struttura)
  • Usa ISerializable, se non puoi modificare il vecchio codice


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow