Разрабатывая распределённое приложение неизбежно сталкиваешься с проблемой передачи разнородных данных между элементами программы. Если данных немного (например массив из 10 000 элементов), особых затруднений не возникает. Однако, с ростом объема скорость взаимодействия между клиентским и серверным приложением стремительно падает.
Сериализация, «встроенная» в .net является его слабым местом. Всего существует три специализированных класса. Первые два, XmlSerializer и SoapFormatter, преобразуют объект в строку в формате Xml. Двоичный образ объекта можно получить, используя BinaryFormatter. Все эти классы характеризуются небыстрой работой и большим объемом избыточных данных в выходной последовательности.
Таким образом, если встаёт необходимость передать большой объем данных, надо искать другое решение. Я создал специализированный интерфейс ISelfSerializable:
/// <summary> /// Интерфейс, определяющий методы, позволяющие объекту самостоятельно сериализовываться в поток и восстанавливаться из него. /// </summary> public interface ISelfSerializable { /// <summary> /// Сериализует объект в поток байт. /// </summary> /// <param name="writer">BinaryWriter используемый для записи в поток.</param> void Serialize(BinaryWriter writer); /// <summary> /// Загружает поля объекта из потока данных. /// </summary> /// <param name="reader">BinaryReader используемый для чтения из потока.</param> void Deserialize(BinaryReader reader); }
Каждый объект, который будет поддерживать «родную» сериализацию, должен его реализовывать. Делается это примерно так:
public class A : ISelfSerializable { public int ID; public string Value; public A() { } public A(int id, string value) { ID = id; Value = value; } public void Serialize(BinaryWriter writer) { writer.Write(ID); writer.Write(Value); } public void Deserialize(BinaryReader reader) { ID = reader.ReadInt32(); Value = reader.ReadString(); } }
А теперь собственно то, ради чего все это задумывалось – вспомогательный класс, позволяющий быстро и безболезненно сериализовывать коллекции объектов:
/// <summary> /// Класс обеспечивает сериализацию списка однотипных объектов в поток, и восстановление списка из потока. /// </summary> public static class SerializationHelper { /// <summary> /// Сериализует перечесление элементов, реализующих ISelfSerializable в массив байт. /// </summary> /// <param name="items">Перечисление элементов.</param> /// <returns>Сериализованные данные.</returns> /// <remarks>Информация о количестве элементов записывается в первых 4 байтах.</remarks> public static byte[] Serialize(IEnumerable<ISelfSerializable> items) { using (MemoryStream ms = new MemoryStream()) { Serialize(items, ms); return ms.ToArray(); } } /// <summary> /// Сериализует перечесление элементов, реализующих ISelfSerializable в поток. /// </summary> /// <param name="items">Перечисление элементов.</param> /// <param name="output">Поток, в который необходимо сериализовать данные.</param> /// <remarks>Информация о количестве элементов записывается в первых 4 байтах.</remarks> public static void Serialize(IEnumerable<ISelfSerializable> items, Stream output) { if (items == null || output == null) throw new ArgumentNullException(items == null ? "items" : "output"); BinaryWriter writer = new BinaryWriter(output); writer.Write(items.Count()); foreach (ISelfSerializable item in items) { item.Serialize(writer); } } /// <summary> /// Восстанавливает элементы. Для корректного использования этого метода необходимо, чтобы информация о количестве элементов была записана перед данными. /// </summary> /// <typeparam name="ObjectType">Тип сериализуемого объекта, должен реализовывать ISelfSerializable.</typeparam> /// <param name="data">Сериализованные данные.</param> /// <returns>Список восстановленных элементов.</returns> public static List<ObjectType> Deserialize<ObjectType>(byte[] data) where ObjectType : ISelfSerializable, new() { if (data == null) throw new ArgumentNullException("data"); using (MemoryStream ms = new MemoryStream(data)) { return Deserialize<ObjectType>(ms); } } /// <summary> /// Восстанавливает элементы. Для корректного использования этого метода необходимо, чтобы информация о количестве элементов была записана перед данными. /// </summary> /// <typeparam name="ObjectType">Тип сериализуемого объекта, должен реализовывать ISelfSerializable.</typeparam> /// <param name="input">Поток сериализованных данных.</param> /// <returns>Список восстановленных элементов.</returns> /// <remarks>Позиция в потоке должна стоять перед данными, включая информацию об их количестве.</remarks> public static List<ObjectType> Deserialize<ObjectType>(Stream input) where ObjectType : ISelfSerializable, new() { if (input == null) throw new ArgumentNullException("input"); BinaryReader reader = new BinaryReader(input); int count = reader.ReadInt32(); List<ObjectType> items = new List<ObjectType>(count); for (int i = 0; i < count; i++) { ObjectType item = new ObjectType(); item.Deserialize(reader); items.Add(item); } return items; } }