C#で設定ファイル書き出しを秒で片づける

# 秒で片づける
以下2つのクラスをコピペします。
コメント書いているので長く見えますが、コード量はわずかです。
使い方は後ろで記述。

``` C#
  public class Config
  {
    /// <summary>
    /// ルートエントリ
    /// </summary>
    public static ConfigEntry Entry = new ConfigEntry() { Key = "ConfigRoot" };
    public static void Load(string filename)
    {
      if (!File.Exists(filename))
        return;
      var xmlSerializer = new XmlSerializer(typeof(ConfigEntry));
      using (var streamReader = new StreamReader(filename, Encoding.UTF8))
      using (var xmlReader = System.Xml.XmlReader.Create(streamReader, new System.Xml.XmlReaderSettings() { CheckCharacters = false }))
      {
        Entry = (ConfigEntry)xmlSerializer.Deserialize(xmlReader); // （3）
      }
    }
    public static void Save(string filename)
    {
      // var arr = data.Select(r => new ConfigEntry() { Key = r.Key, Value = r.Value }).ToArray();
      var serializer = new XmlSerializer(typeof(ConfigEntry));
      using (var streamWriter = new StreamWriter(filename, false, Encoding.UTF8))
        serializer.Serialize(streamWriter, Entry);
    }
  }

```

```C#
  /// <summary>
  /// ConfigEntryクラス。設定の1レコード
  /// </summary>
  public class ConfigEntry
  {
    /// <summary>
    /// 設定レコードののキー
    /// </summary>
    public string Key { get; set; }
    /// <summary>
    /// 設定レコードの値
    /// </summary>
    public string Value { get; set; }
    /// <summary>
    /// 子アイテム
    /// </summary>
    public List<ConfigEntry> Children { get; set; }
    /// <summary>
    /// キーを指定して子アイテムからConfigEntryを取得します。
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public ConfigEntry Get(string key)
    {
      var entry = Children?.FirstOrDefault(rec => rec.Key == key);
      if (entry == null)
      {
        if (Children == null)
          Children = new List<ConfigEntry>();
        entry = new ConfigEntry() { Key = key };
        Children.Add(entry);
      }
      return entry;
    }
    /// <summary>
    /// 子アイテムにConfigEntryを追加します。
    /// </summary>
    /// <param name="key">キー</param>
    /// <param name="o">設定値</param>
    public void Add(string key, string o)
    {
      ConfigEntry entry = Children?.FirstOrDefault(rec => rec.Key == key);
      if (entry != null)
        entry.Value = o;
      else
      {
        if (Children == null)
          Children = new List<ConfigEntry>();
        entry = new ConfigEntry() { Key = key, Value = o };
        Children.Add(entry);
      }
    }
    /// <summary>
    /// 子アイテムからConfigEntryを取得します。存在しなければ新しいConfigEntryが作成されます。
    /// </summary>
    /// <param name="key">キー</param>
    /// <returns></returns>
    public ConfigEntry this[string key]
    {
      set => Add(key, null);
      get => Get(key);
    }
    /// <summary>
    /// 子アイテムからConfigEntryを取得します。存在しなければ新しいConfigEntryが作成されます。
    /// </summary>
    /// <param name="keys">キー、カンマで区切って階層指定します</param>
    /// <returns></returns>
    public ConfigEntry this[params string[] keys]
    {
      set
      {
        ConfigEntry entry = this;
        for (int i = 0; i < keys.Length; i++)
        {
          entry = entry[keys[i]];
        }
      }
      get
      {
        ConfigEntry entry = this;
        for (int i = 0; i < keys.Length; i++)
        {
          entry = entry[keys[i]];
        }
        return entry;
      }
    }

    /// <summary>
    /// 指定したキーが子アイテムに存在するか調べます。再帰的調査はされません。
    /// </summary>
    /// <param name="key">キー</param>
    /// <returns>キーが存在すればTrue</returns>
    public bool Exists(string key) => Children?.Any(c => c.Key == key) ?? false;
    /// <summary>
    /// 指定したキーが子アイテムに存在するか調べます。階層をまたいだ指定をします。
    /// </summary>
    /// <param name="keys">キー、カンマで区切って階層指定します。</param>
    /// <returns>キーが存在すればTrue</returns>
    public bool Exists(params string[] keys)
    {
      ConfigEntry entry = this;
      for (int i = 0; i < keys.Length; i++)
      {
        if (entry.Exists(keys[i]) == false)
          return false;
        entry = entry[keys[i]];
      }
      return true;
    }
  }
```

# 使い方
静的変数Confing.Entryに対して配列の形式でアクセスします。

``` C#:Main.cs
  Config.Entry["設定1"].Value = "値1";//値の設定, 配列の添え字に設定レコードのKey(項目名)を指定する
  Config.Entry["設定1"]["子階層"].Value = "値2";//子階層
  Config.Entry["設定1", "子階層2"].Value = "値3";//子階層、別の書き方
  Config.Save(@"r:\setting.xml");
  
  Config.Load(@"r:\setting.xml");
  string val1 = Config.Entry["設定1"].Value; // => 値1
  string val2 = Config.Entry["設定1"]["子階層"].Value; // => 値2
  string val3 = Config.Entry["設定1", "子階層"].Value;// => 値3
  bool ret1 = Config.Entry.Exists("設定1");  // => true　配列形式で参照するとレコードが作成されてしまうので、Exists関数で問い合わせる。
  bool ret2 = Config.Entry["設定1"].Exists("子階層"); //=> true　"設定1"が存在しない場合、"設定1"は新規作成された上でFalseが返る
  bool ret3 = Config.Entry.Exists("設定1","子階層"); // => true
```
出力

``` xml
<?xml version="1.0" encoding="utf-8"?>
<ConfigEntry xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Key>ConfigRoot</Key>
  <Children>
    <ConfigEntry>
      <Key>設定1</Key>
      <Value>値1</Value>
      <Children>
        <ConfigEntry>
          <Key>子階層</Key>
          <Value>値2</Value>
        </ConfigEntry>
        <ConfigEntry>
          <Key>子階層2</Key>
          <Value>値3</Value>
        </ConfigEntry>
      </Children>
    </ConfigEntry>
  </Children>
</ConfigEntry>
```

# 説明
機能としては以下になります
1. 配列の書式を使用したレコード参照: []を使用したアクセス
2. KeyとValueがペアになった形式。KeyもValueもstringに限定する。型を可変にするくらいなら自前で設定の塊を表現するクラスを作成してシリアライズした方が早い。
2. レコードの自動生成: キーがなかったら、その場で作成されます。
3. レコードの階層化: 子を配列で持つ入れ子構造です。
4. XML出力(変更可能): Save/Loadのシリアライザを変更すれば好きな書式で書き出せます。

ConfigEntry::Childrenは値を入れるまで初期化していませんが、コンストラクタでnewしておくとすべてのレコードで"\<Children />"タグが生成されます。
# コメント
nugetでこのような形式の設定ファイルライブラリはたくさんある。
でも、本記事くらいのシンプルな機能だったらわざわざdll増やさなくても簡単に組み込めそう。
とりあえずちょっと保存したい、位ならこれで十分と思う。