Первая часть цикла статьей о сериализации в .net.
Дальнейшем расширением функциональности класса SerializationHelper является добавление методов, позволяющих сериализовать произвольные типы объектов.
Для начала, определим перечисление, указывающее тип сериализации:
/// <summary> /// Режим сериализации. /// </summary> public enum SerializationMode { /// <summary> /// С использованием XmlSerializer. /// </summary> XML = 1, /// <summary> /// С использованием BinaryFormatter. /// </summary> Binary = 2, /// <summary> /// Запись напрямую через ISelfSerializable. /// </summary> SelfSerializable = 3 }
Всего поддерживается 3 режима – сериализация в Xml с использованием XmlSerializer, двоичная сериализация при помощи BinaryFormatter и ручная сериализация через посредством BinaryWritter.
Теперь рассмотрим методы отвечающие за сериализацию и десериализацию объектов (все методы принадлежат к классу SerializationHelper, рассмотренному в первой части):
/// <summary> /// Сериализует объект в поток. /// </summary> /// <param name="data">Объект для сериализации.</param> /// <param name="output">Выходной поток.</param> /// <param name="mode">Режим сериализации.</param> public static void Serialize(object data, Stream output, SerializationMode mode) { if (data == null || output == null) throw new ArgumentNullException(data == null ? "data" : "output"); Type t; switch (mode) { case SerializationMode.Binary: t = data.GetType(); if(!t.IsSerializable) throw new ArgumentException("data не помечен как сериализуемый", "data"); BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(output, data); break; case SerializationMode.XML: t = data.GetType(); ConstructorInfo ci = t.GetConstructor(Type.EmptyTypes); if(ci == null || !ci.IsPublic) throw new ArgumentException("data не имеет публичного конструктора без параметров", "data"); XmlSerializer xs = SerializerCache.GetSerializer(data.GetType()); xs.Serialize(output, data); break; case SerializationMode.SelfSerializable: ISelfSerializable proxy = data as ISelfSerializable; if(proxy == null) throw new ArgumentException("data не реализует ISelfSerializable", "data"); BinaryWriter writer = new BinaryWriter(output); proxy.Serialize(writer); break; } }
Метод принимает три параметра: объект который необходимо сериализовать, поток в который должна производиться сериализация и режим сериализации. Перед сериализацией происходит проверка, отвечает ли объект минимальным требованиям для последующего восстановления. Перегруженный вариант, просто возвращающий байты сериализованного объекта выглядит так:
/// <summary> /// Сериализует объект. /// </summary> /// <param name="data">Объект для сериализации.</param> /// <param name="mode">Режим сериализации.</param> /// <returns>Байты сериализованного объекта.</returns> public static byte[] Serialize(object data, SerializationMode mode) { //Проверка параметров происходит во внутренней функции using (MemoryStream ms = new MemoryStream()) { Serialize(data, ms, mode); return ms.ToArray(); } }
Теперь рассмотрим десериализацию:
/// <summary> /// Восстанавливает элементы. Для корректного использования этого метода необходимо, чтобы информация о количестве элементов была записана перед данными. /// </summary> /// <typeparam name="ObjectType">Тип восстанавливаемого объекта.</typeparam> /// <param name="data">Байты сериализованных данных.</param> /// <param name="mode">Формат, в котором данные были сериализованны.</param> /// <returns>Список восстановленных элементов.</returns> /// <remarks>Позиция в потоке должна стоять перед данными, включая информацию об их количестве.</remarks> public static ObjectType Deserialize<ObjectType>(byte[] data, SerializationMode mode) { using (MemoryStream ms = new MemoryStream(data)) { return Deserialize<ObjectType>(ms, mode); } } /// <summary> /// Восстанавливает элементы. Для корректного использования этого метода необходимо, чтобы информация о количестве элементов была записана перед данными. /// </summary> /// <typeparam name="ObjectType">Тип восстанавливаемого объекта.</typeparam> /// <param name="input">Поток сериализованных данных.</param> /// <param name="mode">Формат, в котором данные были сериализованны.</param> /// <returns>Список восстановленных элементов.</returns> /// <remarks>Позиция в потоке должна стоять перед данными, включая информацию об их количестве.</remarks> public static ObjectType Deserialize<ObjectType>(Stream input, SerializationMode mode) { if (input == null) throw new ArgumentNullException("input"); Type t = typeof (ObjectType); ConstructorInfo ci; switch (mode) { case SerializationMode.Binary: if (!t.IsSerializable) throw new ArgumentException(t.FullName + " не помечен как сериализуемый"); BinaryFormatter bf = new BinaryFormatter(); return (ObjectType)bf.Deserialize(input); case SerializationMode.XML: ci = t.GetConstructor(Type.EmptyTypes); if (ci == null || !ci.IsPublic) throw new ArgumentException(t.FullName + " не имеет публичного конструктора без параметров"); XmlSerializer xs = SerializerCache.GetSerializer(typeof(ObjectType)); return (ObjectType)xs.Deserialize(input); case SerializationMode.SelfSerializable: ci = t.GetConstructor(Type.EmptyTypes); if (ci == null || !ci.IsPublic) throw new ArgumentException(t.FullName + " не имеет публичного конструктора без параметров"); ISelfSerializable data = ci.Invoke(null) as ISelfSerializable; if (data == null) { throw new ArgumentException(t.FullName + " не реализует ISelfSerializable", "input"); } BinaryReader reader = new BinaryReader(input); data.Deserialize(reader); return (ObjectType)data; default: return default(ObjectType); } }
Последним, пока ещё не рассмотренным элементом является вспомогательный класс SerializerCache. Дело в том, что создание конкретного XmlSerializer достаточно дорого, поэтому имеет смысл кешировать их для частоиспользуемых типов.
/// <summary> /// Кэш для используемых сериалайзеров. /// </summary> public class SerializerCache { private static readonly Dictionary<string, XmlSerializer> _cache = new Dictionary<string, XmlSerializer>(); /// <summary> /// Возвращает кэшированный XmlSerializer. /// </summary> /// <param name="type">Класс, наследуемый от XmlSerializer.</param> public static XmlSerializer GetSerializer(Type type) { XmlSerializer res; lock (_cache) { if (!_cache.TryGetValue(type.FullName, out res)) { res = new XmlSerializer(type); _cache.Add(type.FullName, res); } } return res; } /// <summary> /// Очищает кэш. /// </summary> public static void ClearCache() { lock (_cache) { _cache.Clear(); } } }