C# Language
Двоичная сериализация
Поиск…
замечания
Двоичный механизм сериализации является частью платформы .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, если вы не можете изменить старый код