ritnet.ru

14 Декабрь 2008

Часть 2. Сериализация произвольных объектов

Рубрика: Новости — admin @ 15:58

Первая часть цикла статьей о сериализации в .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();
        }
    }
}

Комментариев нет

Комментариев нет.

RSS-лента комментариев к этой записи.

Извините, обсуждение на данный момент закрыто.

Сайт работает на WordPress