Поиск…


замечания

Двоичный механизм сериализации является частью платформы .NET, но приведенные здесь примеры относятся к C #. По сравнению с другими механизмами сериализации, встроенными в платформу .NET, двоичный сериализатор работает быстро и эффективно и обычно требует очень мало дополнительного кода, чтобы заставить его работать. Однако он также менее терпим к изменениям кода; то есть, если вы сериализуете объект и затем слегка изменяете определение объекта, оно, скорее всего, не будет десериализоваться правильно.

Создание объекта сериализации

Добавьте атрибут [Serializable] чтобы пометить весь объект для двоичной сериализации:

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

    [NonSerialized]
    public decimal DontSerializeThis;

    [OptionalField]
    public string Name;
}

Все члены будут сериализованы, если мы явно не [NonSerialized] атрибута [NonSerialized] . В нашем примере X , Y , Z и Name все сериализованы.

Все члены должны присутствовать при десериализации, если не отмечены [NonSerialized] или [OptionalField] . В нашем примере X , Y и Z являются обязательными, и десериализация не выполняется, если они не присутствуют в потоке. DontSerializeThis всегда будет установлен по default(decimal) (который равен 0). Если Name присутствует в потоке, то он будет установлен в это значение, в противном случае он будет установлен по default(string) (который является нулевым). Цель [OptionalField] - предоставить немного допуск к версии.

Управление сериализацией с атрибутами

Если вы используете [NonSerialized] , этот член всегда будет иметь свое значение по умолчанию после десериализации (например, для int , null для string , false для bool и т. Д.), Независимо от инициализации, сделанной в самом объекте (конструкторы, декларации и т. д.). Для компенсации атрибутов [OnDeserializing] (называемых только ПЕРЕД десериализацией) и [OnDeserialized] (называемых просто после десериализации) вместе со своими аналогами предусмотрены [OnSerializing] и [OnSerialized] .

Предположим, мы хотим добавить «Rating» к нашему Vector, и мы хотим убедиться, что значение всегда начинается с 1. Так, как это написано ниже, после десериализации будет 0:

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

Чтобы устранить эту проблему, мы можем просто добавить следующий метод внутри класса, чтобы установить его в 1:

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

Или, если мы хотим установить его на вычисленное значение, мы можем дождаться завершения десериализации и затем установить его:

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

Точно так же мы можем контролировать, как вещи выписываются с помощью [OnSerializing] и [OnSerialized] .

Добавление большего контроля за счет внедрения ISerializable

Это обеспечит больший контроль над сериализацией, как сохранить и загрузить типы

Внедрить интерфейс ISerializable и создать пустой конструктор для компиляции

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

Для сериализации данных вы можете указать желаемое имя и нужный тип

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

Когда данные десериализованы, вы сможете прочитать нужный тип

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

Сериализация суррогатов (внедрение ISerializationSurrogate)

Реализует сериализационный селектор, который позволяет одному объекту выполнять сериализацию и десериализацию другого объекта

Также позволяет правильно сериализовать или десериализовать класс, который не является сериализуемым

Внедрить интерфейс 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;
    }
}

Затем вы должны сообщить своему знатоку IFormatter о суррогатах, указав и инициализируя SurrogateSelector и назначив его вашему IFormatter

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

Даже если класс не помечен сериализуемым.

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

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

Полное решение

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

Связующее дает вам возможность проверить, какие типы загружаются в ваш домен приложения.

Создайте класс, унаследованный от SerializationBinder

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

Теперь мы можем проверить, какие типы загружаются, и на этой основе решить, что мы действительно хотим получить

Для использования связующего необходимо добавить его в BinaryFormatter.

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

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

Полное решение

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

Некоторые исправления в обратной совместимости

Этот небольшой пример показывает, как вы можете потерять обратную совместимость в своих программах, если заранее не позаботитесь об этом. И способы получения большего контроля над процессом сериализации

Сначала мы напишем пример первой версии программы:

Версия 1

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

А теперь предположим, что во второй версии программы добавлен новый класс. И нам нужно сохранить его в массиве.

Теперь код будет выглядеть так:

Версия 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; }
    }
}

И код для сериализации и десериализации

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

Итак, что произойдет, когда вы сериализуете данные в программе v2 и попытаетесь десериализовать их в программе v1?

Вы получаете исключение:

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

Зачем?

ObjectManager имеет другую логику для разрешения зависимостей для массивов, для ссылочных и значений типов. Мы добавили массив нового ссылочного типа, отсутствующего в нашей сборке.

Когда ObjectManager пытается разрешить зависимости, он строит график. Когда он видит массив, он не может исправить его немедленно, так что он создает фиктивную ссылку, а затем исправляет массив позже.

И поскольку этот тип не находится в сборке, и зависимости не могут быть исправлены. По какой-то причине он не удаляет массив из списка элементов для исправлений и в конце, он выдает исключение «IncorrectNumberOfFixups».

Это некоторые «gotchas» в процессе сериализации. По какой-то причине он работает некорректно только для массивов новых ссылочных типов.

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

И первый способ исправить это и поддерживать совместимость?

  • Используйте коллекцию новых структур, а не классы или используйте словарь (возможные классы), потому что словарь представляет собой набор ключейvaluepair (его структура)
  • Используйте ISerializable, если вы не можете изменить старый код


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow