Szukaj…


Uwagi

Binarny silnik serializacji jest częścią frameworka .NET, ale podane tutaj przykłady są specyficzne dla C #. W porównaniu z innymi silnikami serializacji wbudowanymi w platformę .NET, binarny serializator jest szybki i wydajny i zwykle wymaga bardzo mało dodatkowego kodu, aby go uruchomić. Jest jednak mniej tolerancyjny na zmiany kodu; to znaczy, jeśli serializujesz obiekt, a następnie dokonasz niewielkiej zmiany w definicji obiektu, prawdopodobnie nie zdezrializuje poprawnie.

Uczynienie obiektu możliwym do serializacji

Dodaj atrybut [Serializable] aby oznaczyć cały obiekt do binarnej serializacji:

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

    [NonSerialized]
    public decimal DontSerializeThis;

    [OptionalField]
    public string Name;
}

Wszyscy członkowie zostaną zserializowani, chyba że jednoznacznie zrezygnujemy z użycia atrybutu [NonSerialized] . W naszym przykładzie X , Y , Z i Name są serializowane.

Wszyscy członkowie muszą być obecni podczas deserializacji, chyba że są oznaczone [NonSerialized] lub [OptionalField] . W naszym przykładzie wszystkie X , Y i Z są wymagane, a deserializacja zakończy się niepowodzeniem, jeśli nie będą obecne w strumieniu. DontSerializeThis zawsze będzie ustawione na default(decimal) (czyli 0). Jeśli Name jest obecna w strumieniu, zostanie ustawiona na tę wartość, w przeciwnym razie zostanie ustawiona default(string) (która jest równa null). Celem [OptionalField] jest zapewnienie odrobiny tolerancji wersji.

Kontrolowanie zachowania serializacji za pomocą atrybutów

Jeśli [NonSerialized] atrybutu [NonSerialized] , wówczas ten element członkowski będzie zawsze miał domyślną wartość po deserializacji (np. 0 dla int , null dla string , false dla bool itp.), Niezależnie od jakiejkolwiek inicjalizacji wykonanej w samym obiekcie (konstruktory, deklaracje itp.). Aby to zrekompensować, [OnDeserializing] atrybuty [OnDeserializing] (nazywane po prostu PRZED deserializacją) i [OnDeserialized] (nazywane po prostu PO deserializowaniu) wraz z ich odpowiednikami, [OnSerializing] i [OnSerialized] .

Załóżmy, że chcemy dodać „ocenę” do naszego wektora i chcemy mieć pewność, że wartość zawsze zaczyna się od 1. Sposób, w jaki jest zapisany poniżej, będzie wynosił 0 po deserializacji:

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

Aby rozwiązać ten problem, możemy po prostu dodać następującą metodę w klasie, aby ustawić ją na 1:

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

Lub, jeśli chcemy ustawić wartość obliczoną, możemy poczekać, aż zakończy się deserializacja, a następnie ustawić:

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

Podobnie możemy kontrolować sposób, w jaki rzeczy są spisywane za pomocą [OnSerializing] i [OnSerialized] .

Dodanie większej kontroli poprzez wdrożenie ISerializable

Zapewniłoby to większą kontrolę nad serializacją, sposobem zapisywania i ładowania typów

Zaimplementuj interfejs ISerializable i utwórz pusty konstruktor do kompilacji

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

W przypadku serializacji danych można określić żądaną nazwę i żądany typ

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

Gdy dane zostaną przekształcone w postaci szeregowej, będziesz w stanie odczytać pożądany typ

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

Zastępstwa serializacji (Implementing ISerializationSurrogate)

Implementuje selektor zastępowania serializacji, który pozwala jednemu obiektowi na serializację i deserializację innego

Pozwala również na prawidłowe serializowanie lub deserializację klasy, która sama nie jest serializowalna

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

Następnie musisz poinformować swój IFormatter o surogatach poprzez zdefiniowanie i zainicjowanie SurrogateSelector i przypisanie go do swojego IFormatter

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

Nawet jeśli klasa nie jest oznaczona jako możliwa do serializacji.

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

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

Kompletne rozwiązanie

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

Spoiwo daje możliwość sprawdzenia, jakie typy są ładowane w domenie aplikacji

Utwórz klasę odziedziczoną z SerializationBinder

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

Teraz możemy sprawdzić, jakie typy ładują się i na tej podstawie zdecydować, co naprawdę chcemy otrzymać

Aby użyć spoiwa, musisz dodać go do BinaryFormatter.

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

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

Kompletne rozwiązanie

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

Niektóre problemy z kompatybilnością wsteczną

Ten mały przykład pokazuje, w jaki sposób można utracić kompatybilność wsteczną w swoich programach, jeśli nie podejmie się tego z wyprzedzeniem. I sposoby uzyskania większej kontroli nad procesem serializacji

Najpierw napiszemy przykład pierwszej wersji programu:

Wersja 1

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

A teraz załóżmy, że w drugiej wersji programu dodano nową klasę. I musimy przechowywać go w tablicy.

Teraz kod będzie wyglądał następująco:

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

I kod do serializacji i deserializacji

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

A więc, co by się stało, gdy serializujesz dane w programie v2 i spróbujesz deserializować je w programie v1?

Otrzymujesz wyjątek:

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

Dlaczego?

ObjectManager ma inną logikę do rozwiązywania zależności dla tablic oraz typów referencyjnych i wartościowych. Dodaliśmy tablicę nowego typu odniesienia, którego nie ma w naszym zestawie.

Kiedy ObjectManager próbuje rozwiązać zależności, tworzy wykres. Kiedy widzi tablicę, nie może jej natychmiast naprawić, więc tworzy fałszywe odniesienie, a następnie naprawia tablicę później.

A ponieważ tego typu nie ma w zestawie, a zależności nie można naprawić. Z jakiegoś powodu nie usuwa tablicy z listy elementów dla poprawek, a na końcu zgłasza wyjątek „IncorrectNumberOfFixups”.

To pewne „gotchas” w procesie serializacji. Z jakiegoś powodu nie działa poprawnie tylko dla tablic nowych typów referencyjnych.

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

A pierwszy sposób, aby to naprawić i zachować zgodność?

  • Użyj kolekcji nowych struktur zamiast klas lub użyj słownika (możliwe klasy), ponieważ słownik jest zbiorem pary kluczy-wartości (jego struktura)
  • Użyj ISerializable, jeśli nie możesz zmienić starego kodu


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow