По просьбе автора две части статьи были соединены в одну тему.
Все комментарии были перемещены в отдельную тему. Просьба комментарии теперь писать только туда, чтобы не засорять этот топик!
- АВТОМАТ
Сериализация обьектов
.Вступление
Думаю много кто из вас делал загрузку/сохранение пареметров программы/игры, и часто сталкивался с разными проблемами связаными с этим делом.
Хотелось поделится опытом, услышать от вас идеи по улучшению.
1. Ввод в сериализацию
Как гласит вика
"Сериализация (в программировании) — процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации — восстановление начального состояния структуры данных из битовой последовательности."
То есть это сохранения наших классов, с их, состоянием в файл. C# даёт удобную(но далеко не идеальную) возможность для работы с сериализцией
System.Xml.Serialization. Он записывает данные в файл в виде Xml структуры, что удобно, если нужно будет править файл вручную, и в общем-то читабельней.
Минусом использования этого класса является то, что на сериализацию больших классов многократно (дупустим некое подобие базы данных) будет тратится довольно таки ощутимое время, что не совсем хорошо.
Давайте попробуем обойти это
2.
Один из способов избежать повторной сериализации структуры - сохранять шаблон, и при следующем вызове сериализации использовать его.
Сейчас напишем базовый класс для нашого сериализатора.
public class XmlHelper
{
#region members
#endregion
#region Static
public static XmlSerializer GetSerializer(Type type)
{
return null;
}
public static void WriteEntity(object obj, string fileFullPath)
{
}
public static T GetEntity<T>(string fileFullPath)
{
return null;
}
#endregion
}
Примечание: как видите в коде используется
#region советую и вам его использовать, это облегчает навигацию по коду, а также даёт возможность свернуть группы( например методы, поля, свойства и т.д.) чтобы нельзя было спутать их с другими участками кода
Примечание 2:для сериализатора нужно будет использовать следующие библиотеки
#region using
using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml.Serialization;
#endregion
Все методы объявлены как статик, потому что пока что нам не нужно создавать несколько экземпляров помощника, и можно будет обращаться из любого участка кода для сериализации нужного нам объекта.
Итак, давайте разберём по методом, что у нас есть. А есть у нас всего три метода, которые, пока что, как заглушки:
GetSerializer(Type type) - метод который будет возвращать сериализатор для указанного типа - "Type type"
WriteEntity(object obj, string fileFullPath) - метод который будет записывать в файл полученный объект (
object obj) любого типа в файл (
string fileFullPath). Путь к файлу желательно писать полный, во избежания недоразумений.
T GetEntity<T>(string fileFullPath) - метод который будет производить десериализацию указанного файла (string fileFullPath) в тип T, и возвратит нам экземпляр этого класса(
T).
Давайте наполнять наш класс.
Во первых, создадим хранилище для сериализаторов, в таком деле я предпочитаю использовать коллекцию типа
Hashtable (хотя тут дело каждого можно использовать и обычный
IDictionary<string,XmlSerializer>)
private static readonly Hashtable hash = new Hashtable();
сделаем её приватной чтобы не было доступа не из нашего класса сериализатора (безопасный код, это важная вещь) и не было соблазна что-то засунуть туда.
Так как переменная у нас
static, то можно сразу же её объявить, чтобы потом не переживать (хотя я рекомендую всё-таки делать конструкторы даже для статик классов).
Идем дальше. Заходим в метод
GetSerializer
Обьявляем переменную для хранения возвращаемого сериализатора
далее, так как приложение может быть многопоточное, нужно обезопасить себя, чтобы одновременно несколько обьектов не попытались получить сериализатор из нашей коллекции, так как это вызовет Ошибку.
и так лочим нашу коллекцию
команда
lock пытается получить к объекту (hash) монопольный доступ, и не начнет выполнение последующих за ней операторов, пока не получит этот самый доступ.
Внутри попытаемся получить сериализатор из нашей коллекции, в которой сохранены имена типов, который уже были сериализованы, и собственно сам сериализатор.
res = hash[type.FullName] as XmlSerializer;
так как в коллекции хранятся объекты в виде
object, пытаемся привести его к XmlSerializer. если в колеции не будет этого типа, то переменная res будет равна
null. идём дальше, проверим не равняется ли res null, и если равняется, прийдётся нам всётаки получить у системы сериализатор даного нудного класса.
if (res == null)
{
res = new XmlSerializer(type);
hash[type.FullName] = res;
}
после получения сериализатора записываем его в коллекцию сериализаторов с ключём
type.FullName - который соответствует полному имени класса, для которго мы создали сериализатор.
Вот собственно как должен после всего этого выглядеть метод
public static XmlSerializer GetSerializer(Type type)
{
XmlSerializer res;
lock (hash)
{
res = hash[type.FullName] as XmlSerializer;
if (res == null)
{
res = new XmlSerializer(type);
hash[type.FullName] = res;
}
}
return res;
}
Теперь давайте заполним метод
WriteEntity, который записывает класс в файл.
Для начала создаём сериализатор, с использованием только что написанного нами метода.
XmlSerializer serializer = GetSerializer(obj.GetType());
Далее создадим
StringBuilder, который помогает работать со строками и кодировками.
StringBuilder stringBuilder = new StringBuilder();
Также нужно создать класс-поток который производить работу со строками и передадим ему наш билдер строк.
StringWriter stringWriter = new StringWriter(stringBuilder , CultureInfo.InvariantCulture);
Начинаем сериализацию обьекта obj в поток:
serializer.Serialize(stringWriter , obj);
Закрываем поток:
Всё информация в виде строк сохранилась в наш билдер строк -stringBuilder, давайте получим всё в одну строку.
string xml = stringBuilder .ToString();
Создадим экземпляр класса, который производить работу с записью информации в файл. Передадим в конструктор имя нужного нам файла который получили в параметрах метода:
StreamWriter sw = new StreamWriter(fileFullPath);
Записываем:
И не забываем закрыть поток.
Результат в методе -
public static void WriteEntity(object obj, string fileFullPath)
{
XmlSerializer sr = GetSerializer(obj.GetType());
StringBuilder sb = new StringBuilder();
StringWriter w = new StringWriter(sb, CultureInfo.InvariantCulture);
sr.Serialize(w, obj);
w.Close();
string xml = sb.ToString();
StreamWriter sw = new StreamWriter(fileFullPath);
sw.Write(xml);
sw.Close();
}
Вот собственно и всё, мы сделали сериализацию объекта в файл.
Работать с этим классом так
XmlHelper.WriteEntity(MyClass,"С:\pron\list.extention");
Хочу заметить, что сериализуються ТОЛЬКО публичные свойства и поля класса(public) и называются они в файле также как вы назовёте их в коде.
Пока что всё.
Завтра напишу Про то как производить десериализацию из файла( а лучше не ждите и попробуйте сами сделать). а также про то, как назначить имена при сериализации и прочие нюансы. Удачи!
З.Ы. Спасибо
ffinder за корректуру.