Итак, есть у нас некий набор параметров программы, который надо сохранить при задании его пользователем, и восстановить при запросе из основной программы, т.е. файл конфигурации.
Обычно под управление конфигурацией делается отдельный класс, задача которого сохранить/загрузить конфиг и в нужный момент выдать запрашиваемый параметр. В качестве хранилища данных можно использовать 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();
}
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
Pingback: C#. О конфигах и сохранении/загрузке свойств объекта, часть 2 | Персональный блог Толика Панкова