Толик Панков
hex_laden
............ .................. ................

October 2025
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Толик Панков [userpic]
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

Это репост с сайта http://tolik-punkoff.com
Оригинал: http://tolik-punkoff.com/2018/05/15/c-o-konfigah-i-sohranenii-zagruzke-svojstv-obekta/

Tags: ,
Comments
(Anonymous)

Разосрался кодом так будто кормили не кетмара а тебя.

Дык нас эти... рептилоиды подкармливают. Надо отрабатывать.

А DataSet ты потом в интерфейсе для редактирования конфига используешь, или как?

Пока "или как", с формы пока все в прежнем режиме делается. Т.е. пользователь заполняет форму, по нажатию на OK проверяю корректность ввода данных и присваиваю значения полям объекта, который занимается конфигом. С формами надо бы тоже как-то все улучшить и углУбить, но пока не придумал как.

Я к тому, что если ты его нигде в чистом виде не используешь, то занафига городить огород с датасетом и рефлекцией?
Делаешь класс хранения с публичными полями, хинтишь его как Serializable (а, скажем, ненужные его члены хинтишь как NonSerialized), и любым стандартным форматтером выплёвываешь. Хочешь двоичным, хочешь xml. А если таки поднимешься до .NET 3.5 (который в принципе тоже не новый, и на старых машинах вполне идёт, а ещё в нём LINQ), то и в JSON выплюнет.

Надо попробовать. Авось, еще меньше кода будет. Я на DataSet польстился уже давно, именно из-за того, что он в/из XML сам умеет, без форматтеров.

Кстати, рефлекцией победил сбор данных с формы. Теперь достаточно контролы на форме назвать правильно (типа txtНужнаяПропертьВВыходномКлассе, chkНужнаяПропертьВВыходномКлассе или rbНужнаяПропертьВВыходномКлассеИмяЗначенияВEnum), скормить объект формы и выходной объект (например объект класса конфигурации) промежуточному классу, и он из формы все вынет, в объект положит, или же наоборот. Остается только дописать проверки по типу, "Вы забыли ввести эту хрень. Срочно введите хрень в поле хрень!"

Вот, это уже красиво. Автоматизация -- наше всьо!

Сделал то же самое и для записи в DataTable, теперь не надо еще и биндить это все через всякие костыли студии. Не понимал, как они работают, какой код там генерят и т.д. Проще (хуй с ним, что забыдлокодить) какую-то понятную вещь один раз, потом только знай - нэймспейс в файле подправляй. У меня так учитель модули в Pascal под DOS делал. Один раз сделал, потом только в проекты подключал. Правда в Pascal не было нэймспейсов.

Правда, получилось не совсем универсально. Но если на форме основные компоненты - чекбоксы, текстовые поля (в них можно и числовые значения вводить, ограничив ввод символов, и например, для того же СНИЛС или номера карты это удобнее, чем городить отдельный огород), и радиокнопки - выбор из нескольких вариантов, варианты лучше ложить в enum.

Кстати да, благодарю, кода стало еще меньше!
Правда, ты меня дезинформировал. %) NonSerialized работает только на поля, но не на свойства. На свойства работает [XmlIgnore], впрочем, приватные свойства (даже те, у которых приватное что-то одно, или get или set, а само свойство публичное) и приватные поля сериализатор XML игнорит вообще.

Ну звиняй, всех тонкостей не помню, давно этим занимался.