C#. О конфигах и сохранении/загрузке свойств объекта, часть 2

В прошлой части Копия я рассказывал, как с помощью инструментов из пространства имен 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 Framework

Сериализатор 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;
    }
}

Исходник примера на GitHub

AppSettings.cs

Источники

Киберфорум
Сериализация в XML. XmlSerializer

Навел на мысль [info]steinkrauz@ljr

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *