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

Итак, есть у нас некий набор параметров программы, который надо сохранить при задании его пользователем, и восстановить при запросе из основной программы, т.е. файл конфигурации.
Обычно под управление конфигурацией делается отдельный класс, задача которого сохранить/загрузить конфиг и в нужный момент выдать запрашиваемый параметр. В качестве хранилища данных можно использовать DataSet. Во-первых, потому что все параметры можно представить в удобном виде типа таблицы базы данных, а во-вторых, DataSet умеет сохранять свое содержимое в XML и загружать его в автоматическом режиме. Но вот с заполнением DataSet возникают некоторые проблемы. Обычно я заполнял его почти вручную, что приводило к появлению некрасивых простыней кода, в которых, к тому же, легко допустить опечатку. А добавление нового параметра, приводило к необходимости добавлять его в несколько мест в коде.

Как оказалось, можно все сделать гораздо проще — обойти в автоматическом режиме все поля класса, собрать или загрузить в них все нужные значения, а также построить таблицу в DataSet, содержащую все необходимые поля нужного типа.

Что потребуется

Надо подключить пространства имен System.Data и System.Reflection

using System.Data;
using System.Reflection;

и завести приватные переменные, собственно DataSet, переменную под имя конфиг-файла и переменную под имя таблицы:

private string configFile = "";
private string TableName = "";
private DataSet dsNetConfig = new DataSet();

Далее в конструкторе класса запрашиваем имя файла конфигурации, формируем имя таблицы и вызываем функцию, создающую таблицу DataSet со всеми необходимыми полями, которые будут хранить значения свойств класса.

public NetSettings(string filename)
{            
    configFile = filename;
    TableName = this.GetType().Name;
    CreateDataSet();
}

Создание таблицы DataSet

private void CreateDataSet()
{             
    dsNetConfig.Tables.Add(TableName);
    
    PropertyInfo[] properties = this.GetType().GetProperties();

    foreach (PropertyInfo pr in properties)
    {
        dsNetConfig.Tables[TableName].Columns.Add(pr.Name,
        pr.PropertyType);
    }
}

— Добавляем в DataSet таблицу
— Получаем список свойств класса в виде массива PropertyInfo:

PropertyInfo[] properties = this.GetType().GetProperties();

— В цикле foreach создаем колонки в таблице DataSet, задавая имя и тип данных:

dsNetConfig.Tables[TableName].Columns.Add(pr.Name, pr.PropertyType);

Сохранение данных

public bool SaveConfig()
{
    // [...]
    
	
    ConfigError = null;
    dsNetConfig.Tables[TableName].Rows.Clear();
    DataRow dr = dsNetConfig.Tables[TableName].NewRow();
    
    
    PropertyInfo[] properties = this.GetType().GetProperties();
    foreach (PropertyInfo pr in properties)
    {
        string propName = pr.Name;
        object propValue = pr.GetValue(this,null);
        dr[propName] = propValue;
    }

    dsNetConfig.Tables[TableName].Rows.Add(dr);

    try
    {
        dsNetConfig.WriteXml(configFile);
    }
    catch (Exception ex)
    {
        ConfigError = ex.Message;
        return false;
    }
    
    return true;
}

— Добавляем в таблицу новый DataRow (у меня строка должна быть всего одна, потому для начала очищаю содержимое таблицы на всякий случай)

dsNetConfig.Tables[TableName].Rows.Clear();
DataRow dr = dsNetConfig.Tables[TableName].NewRow();

— Далее опять же получаю массив PropertyInfo и обрабатываю его в цикле foreach
— Получаю имя поля:

string propName = pr.Name;

— И его значение:
object propValue = pr.GetValue(this,null);

Второй параметр в функции GetValue — индекс для свойств, имеющих индексацию. Например, если свойство является массивом, то нужно будет задавать индекс элемента для получения конкретного значения. В данном случае таких свойств в классе нет, посему в качестве второго параметра указывается null.

— Записываю значение на свое место в DataSet:

dr[propName] = propValue;

— Добавляю в таблицу сформированную строку:

dsNetConfig.Tables[TableName].Rows.Add(dr);

— Сохраняю содержимое DataSet в XML:

dsNetConfig.WriteXml(configFile);

Что делать с ненужными в конфиге полями класса

В разбираемом примере есть такое свойство public string ConfigError, хранящее сообщение об ошибке при загрузке/сохранении конфига, но в самом конфигурационном файле не нужное.

Тут три пути:

1. Самый простой. Забить и плюнуть, ненужное свойство будет сохраняться в конфиге, загружаться из него, да и пусть.
2. Компромиссный. Обнулить свойство перед сохранением. Тогда оно будет как поле в таблице DataSet, но в конфиге его не будет. Так сделано в разбираемом классе Способ хорош, если таких ненужных свойств мало. И место в файле оно занимать не будет, и не нужны дополнительные проверки.
3. Составить список, хоть в виде строковой переменной, где перечислить «лишние» поля, и проверять список перед сохранением и созданием таблицы.

Загрузка данных из конфига

public NetConfigStatus LoadConfig()
{
	//[...]
    try
    {
        dsNetConfig.ReadXml(configFile);
    }
    catch (Exception ex)
    {
        ConfigError = ex.Message;
        return NetConfigStatus.Error;
    }

    //загрузка свойств класса из DataSet
    if (dsNetConfig.Tables[TableName].Rows.Count > 0)
    {
        PropertyInfo[] properties = this.GetType().GetProperties();
        foreach (PropertyInfo pr in properties)
        {
            string propName = pr.Name;
            object propValue = dsNetConfig.Tables[TableName].Rows[0][propName];
            if (propValue.GetType() != typeof(System.DBNull))
            {
                pr.SetValue(this, propValue, null);
            }
        }
		
    //[...]    
    }

    return NetConfigStatus.OK;
}

Все делается точно также, только в обратном порядке.
— Загружаем XML
— Получаем список свойств
— Устанавливаем значения в цикле с помощью SetValue

Необходимы только две проверки — на количество записей в таблице DataSet и на то, не является ли значение ячейки DBNull

Весь код класса на PasteBin

Источники

Киберфорум
MSDN

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

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

*