В прошлой части Копия я рассказывал, как с помощью инструментов из пространства имен System.Reflection
, можно сохранить свойства объекта, например, в таблицу DataSet
, или наоборот, загрузить из DataSet
данные в свойства соответствующего объекта. Таким образом, решалась проблема автоматизации работы с конфигурационными файлами.
Есть способ еще более уменьшить количество кода, воспользовавшись стандартным механизмом .NET Framework — сериализацией. Сериализация, это, по рабоче-крестьянски говоря, именно что сохранение состояния объекта (он же пафосно называется «экземпляром класса») в некий передаваемый формат. Доскональное объяснение, что это такое, в статью не влезет, потому оставим.
Итак, переходим к сериализации.
В .NET Framework сам себя класс сериализовать не может, точнее, сериализовать-то может, а вот десериализовать — нифига. Класс и его экземпляр, получается, как Штирлиц с раненой радисткой Кэт, передать могут, а обратно нет, без дружественной помощи.
Поэтому, придется изобретать выход, в котором классов будет больше, а кода меньше, чем в предыдущем случае
Выход в том, чтобы создать класс, непосредственно хранящий конфигурацию, и класс-менеджер, который возьмет на себя работу над сериализацией, десериализацией и другим, например, назначением значений по умолчанию.
Итак, создадим класс-хранилище, вот такой вот, например:
[Serializable] public class AppSettings { public string DataUrl { get; set; } public FormatType DataFormat { get; set; } public string IPColumn { get; set; } public string FieldSeparator { get; set; } public string FlagColumn { get; set; } public string TrueValue { get; set; } public string FalseValue { get; set; } public bool LoadUpdate { get; set; } public AppSettings() { } }
Если нужно, чтобы класс сериализовался, то перед описанием класса нужно обязательно установить атрибут «сериализуемый»:
[Serializable]
Далее нам надо выбрать сериализатор, т.е. набор методов, который будет данные нашего класса во что-нибудь преобразовывать, или наоборот, восстанавливать, загружая ранее сохраненные значения.
Ну, раз уж в прошлый раз, мы выбирали XML, то и сейчас я буду сериализовывать класс в XML
Сериализатор XML в .NET пропускает все приватные поля и свойства класса. На мой взгляд, это больше хорошо, чем плохо, если сохранять класс, хранящий набор параметров конфигурации — приватные все равно не нужны.
Если какое-то публичное свойство не надо сериализировать, т.е. в нашем случае, сохранять в конфиг, то такому свойству надо установить атрибут [XmlIgnore]
, для публичных полей устанавливается атрибут [NonSerialized]
.
Например:
[XmlIgnore]
public string DataUrl { get; set; }
Последняя особенность сериализации в XML — сериализируемому классу необходим конструктор без параметров, причем, практически выявлено, в этом конструкторе лучше вообще ничего не делать, особенно того, что может привести к ошибкам времени выполнения. Иначе получите гадскую невыявляемую ошибку, поскольку в отладке у вас будет всякая фигня, кроме того, чего нужно.
Я так понимаю, конструктор без параметров вызывается во время XML-сериализации, и наверняка, в нем что-то можно и нужно делать, но пока я не нашел, как и где это подробно расписать.
Само дерево жужжать не может
Значит, кто-то тут жужжит
(Вини-Пух)
Это самая гадская особенность сериализации в .NET, а именно, если в прошлом случае могли параметры конфига, и функции для их сохранения-загрузки объединить в один класс, то в подходе с использованием сериализации не можем:
this = (Data)readerRr.Deserialize(fileRr);
this - переменная только для чтения, по крайней мере до .NET 4.0 включительно.
И такой подход считается «плохим дизайном», хотя на мой нескромный взгляд, плохой дизайн — это разносить части одного и того же по разным классам.
Но раз уж надо, значит надо. Делаем класс-менеджер:
Далее такой условный класс-менеджер с возможностью сохранения и загрузки:
public class AppSettingsManager { private string fileName = ""; public string ConfigError { get; private set; } public AppSettings Settings = new AppSettings(); public AppSettingsManager(string filename) { //устанавливаем значения по умолчанию // и имя файла конфигурации } public bool SaveConfig() { FileStream fs = null; /... XmlSerializer formatter = new XmlSerializer(typeof(AppSettings)); try { fs = new FileStream(fileName, FileMode.Create, FileAccess.Write); formatter.Serialize(fs, Settings); } catch (Exception ex) { ConfigError = ex.Message; return false; } fs.Close(); return true; } public bool LoadConfig() { FileStream fs = null; XmlSerializer formatter = new XmlSerializer(typeof (AppSettings)); if (!File.Exists(fileName)) return true; try { fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); Settings = (AppSettings)formatter.Deserialize(fs); } catch (Exception ex) { if (fs != null) fs.Close(); ConfigError = ex.Message; return false; } fs.Close(); return true; } }
Киберфорум
Сериализация в XML. XmlSerializer
Навел на мысль steinkrauz@ljr