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