C Sharpの基礎 - INIファイル

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

INIファイルは、シンプルな設定ファイル形式であり、多くのアプリケーションで使用されている。
その構造は直感的で、セクションとキーおよび値のペアで構成されている。

基本的な構造として、INIファイルはセクションで始まり、その下にキーと値のペアが続く。
セクションは角括弧[]で囲まれ、キーと値はイコール記号=で区切られる。

例えば、以下に示すような形式になる。

 [<セクション名>]
 <キー名 1>=<値>
 <キー名 2>=<値>


C#でINIファイルを読み書きするには、主に2つのアプローチがある。

  • C#では、INIファイルを直接サポートする組み込みのクラスは存在しないため、System.IO名前空間のクラスを使用して開発者自身で実装する方法
  • サードパーティ製のライブラリを使用する方法


開発者自身で実装する場合、File.ReadAllLinesメソッドを使用してファイルの内容を読み込み、文字列操作でセクション、キー、値を解析する。
書き込む場合は、File.WriteAllLinesメソッドを使用する。

サードパーティ製のライブラリを使用する場合、多くの選択肢があるが、人気のあるものの1つにini-parserライブラリが存在する。
これは、NuGetパッケージマネージャーを通じてインストールでき、INIファイルの読み書きを効率的に行うことができる。

INIファイルは簡単な設定の保存に便利であるが、複雑な構造や大量のデータを扱う場合は、XMLやJSON等の他の形式を使用することを推奨する。


実装する場合

INIファイルの読み込み

以下の例では、INIファイルを非同期で読み込みするクラスを定義している。
また、必要に応じて、バリデーションやより詳細なエラーハンドリングを追加することができる。

下記の特徴を以下に示す。

  • 非同期処理
    ファイルの読み込みを非同期で行う。
  • ストリーミング処理
    FileStreamクラス、StreamReaderクラスを使用してファイルをストリーミング処理する。
    これにより、大きなファイルでもメモリ効率よく処理することができる。


 using System;
 using System.IO;
 using System.Text;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 
 public class IniFileReader
 {
    // INIファイルの内容を保持する辞書
    private Dictionary<string, Dictionary<string, string>> _iniContent;
 
    public IniFileReader()
    {
        _iniContent = new Dictionary<string, Dictionary<string, string>>();
    }
 
    // INIファイルを非同期で読み込む
    public async Task ReadIniFileAsync(string filePath)
    {
       try
       {
          using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
          using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
          {
             string currentSection = "";
             string line;
 
             while ((line = await streamReader.ReadLineAsync()) != null)
             {
                line = line.Trim();
 
                // コメント行 (; #) や空行を無視
                if (string.IsNullOrWhiteSpace(line) || line.StartsWith(";") || line.StartsWith("#"))
                   continue;
 
                // セクション行の処理
                if (line.StartsWith("[") && line.EndsWith("]"))
                {
                   currentSection = line.Substring(1, line.Length - 2);
                   if (!_iniContent.ContainsKey(currentSection))
                      _iniContent[currentSection] = new Dictionary<string, string>();
                }
                else if (line.Contains("="))
                {  // キーおよび値ペアの処理
                   var parts = line.Split(new[] { '=' }, 2);
                   if (parts.Length == 2)
                   {
                      var key = parts[0].Trim();
                      var value = parts[1].Trim();
                      _iniContent[currentSection][key] = value;
                   }
                }
             }
          }
       }
       catch (FileNotFoundException)
       {
          Console.WriteLine("指定されたファイルが存在しない");
       }
       catch (IOException ex)
       {
          Console.WriteLine($"ファイルの読み込み中にエラーが発生: {ex.Message}");
       }
       catch (Exception ex)
       {
          Console.WriteLine($"予期せぬエラーが発生: {ex.Message}");
       }
    }
 
    // 値を取得するメソッド
    public string GetValue(string section, string key)
    {
       if (_iniContent.TryGetValue(section, out var sectionDict))
       {
          if (sectionDict.TryGetValue(key, out var value))
          {
             return value;
          }
       }
 
       return null;
    }
 
    // 読み込んだINIファイルの内容を表示するメソッド
    public void DisplayContent()
    {
       foreach (var section in _iniContent)
       {
          Console.WriteLine($"[{section.Key}]");
          foreach (var kvp in section.Value)
          {
             Console.WriteLine($"{kvp.Key}={kvp.Value}");
          }
          Console.WriteLine();
       }
    }
 }


上記のクラスを使用する場合は、以下に示すようにインスタンスを生成して、メソッドを実行する。

 var iniReader = new IniFileReader();
 
 // INIファイルの読み込み
 await iniReader.ReadIniFileAsync("sample.ini");
 
 // 値の取得
 string value = iniReader.GetValue("セクション名", "キー名");
 
 // 値の表示
 iniReader.DisplayContent();


INIファイルの書き込み

以下の例では、INIファイルを非同期で書き込みするクラスを定義している。
また、必要に応じて、バリデーションやより詳細なエラーハンドリングを追加することができる。

下記の特徴を以下に示す。

  • 非同期処理
    ファイルの読み込みを非同期で行う。
  • ストリーミング処理
    FileStreamクラス、StreamWriterクラスを使用してファイルをストリーミング処理する。
    これにより、大きなファイルでもメモリ効率よく処理することができる。


 using System;
 using System.IO;
 using System.Text;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 
 public class IniFileWriter
 {
    // INIファイルの内容を保持する辞書
    private Dictionary<string, Dictionary<string, string>> _iniContent;
 
    public IniFileWriter()
    {
       _iniContent = new Dictionary<string, Dictionary<string, string>>();
    }
 
    // INIファイルを非同期で書き込む
    public async Task WriteIniFileAsync(string filePath)
    {
       try
       {
          using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
          using (var streamWriter = new StreamWriter(fileStream, Encoding.UTF8))
          {
             foreach (var section in _iniContent)
             {
                await streamWriter.WriteLineAsync($"[{section.Key}]");
                foreach (var kvp in section.Value)
                {
                   await streamWriter.WriteLineAsync($"{kvp.Key}={kvp.Value}");
                }
                await streamWriter.WriteLineAsync();  // セクション間に空行を挿入
             }
          }
          Console.WriteLine("INIファイルの書き込みが完了");
       }
       catch (UnauthorizedAccessException)
       {
          Console.WriteLine("ファイルへの書き込み権限がない");
       }
       catch (IOException ex)
       {
          Console.WriteLine($"ファイルの書き込み中にエラーが発生: {ex.Message}");
       }
       catch (Exception ex)
       {
          Console.WriteLine($"予期せぬエラーが発生: {ex.Message}");
       }
    }
 
    // 値を設定する
    public void SetValue(string section, string key, string value)
    {
       if (!_iniContent.ContainsKey(section))
       {
          _iniContent[section] = new Dictionary<string, string>();
       }
       _iniContent[section][key] = value;
    }
 
    // セクションを追加する
    public void AddSection(string section)
    {
       if (!_iniContent.ContainsKey(section))
       {
          _iniContent[section] = new Dictionary<string, string>();
       }
    }
 
    // セクションを削除する
    public void RemoveSection(string section)
    {
       _iniContent.Remove(section);
    }
 
    // キーを削除する
    public void RemoveKey(string section, string key)
    {
       if (_iniContent.ContainsKey(section))
       {
          _iniContent[section].Remove(key);
       }
    }
 }


上記のクラスを使用する場合は、以下に示すようにインスタンスを生成して、メソッドを実行する。

 var iniWriter = new IniFileWriter();
 
 // セクションを追加する
 iniWriter.AddSection("新しいセクション");
 
 // セクション, キー, 値をセット
 iniWriter.SetValue("新しいセクション", "新しいキー", "新しい値");
 
 // INIファイルへ書き込む
 await iniWriter.WriteIniFileAsync("sample.ini");



ini-parserライブラリ

ini-parserライブラリとは

ini-parserライブラリは、C#で開発されたオープンソースのライブラリであり、INIファイルの読み書きを簡単に行うことができる。
このライブラリは、INIファイルの構造を解析して、扱いやすいオブジェクトモデルに変換する機能を提供している。

ini-parserライブラリの主な特徴として、シンプルで直感的なAPIを持つことが挙げられる。
開発者は、複雑な文字列操作やファイル操作を行うことなく、INIファイルの内容を簡単に操作することができる。

このライブラリは、セクション、キー、値の概念を自然にサポートしており、INIファイルの階層構造を忠実に表現する。

ライブラリの中心となるのは、FileIniDataParserクラスである。
このクラスを使用することにより、INIファイルの読み込みと書き込みを行うことができる。

読み込んだデータはIniDataオブジェクトとして表現され、このオブジェクトを通じてINIファイルの内容にアクセスおよび変更することが可能である。

ini-parserライブラリは、コメントの保持やフォーマットの維持等、INIファイルの構造を尊重する機能も備えている。
これにより、既存のINIファイルを読み込んで変更を加え、元のフォーマットを保ったまま書き出すことができる。

エラーハンドリングも考慮されており、ファイルの読み込みや書き込み時に発生する可能性のある例外を適切に処理できるようになっている。
また、無効なINIファイルの構造に対しても、可能な限り柔軟に対応するよう設計されている。

パフォーマンスの面でも、ini-parserライブラリは効率的に動作するよう最適化されている。
大きなINIファイルを扱う場合でも、メモリ使用量を抑えつつ高速に処理を行うことができる。

さらに、ini-parserライブラリは拡張性も考慮されており、カスタムのパーサーや書き込みロジックを実装することも可能である。
これにより、特殊なフォーマットのINIファイルや、INIファイルに似た独自フォーマットのファイルにも対応することができる。

ini-parserライブラリを使用することにより、開発者はINIファイルの処理に関する低レベルの詳細に煩わされることなく、アプリケーションのロジックに集中することができる。
シンプルな設定ファイルから複雑な構成ファイルまで、幅広いシナリオでini-parserライブラリは有効なライブラリとなっている。

ini-parserライブラリは、MITライセンスに準拠している。

ini-parserライブラリのインストール

RiderまたはVisual StudioからNuGetを使用して、ini-parserライブラリをインストールする。

  • Riderの場合
    1. プロジェクトを開く。
    2. [ツール]メインメニュー - [Nuget] - [ソリューション の Nuget パッケージを管理] (または、[<プロジェクト名> の Nuget パッケージを管理])を選択する。
    3. メイン画面下部にある[パッケージ]タブから ini-parser と入力して検索する。
    4. メイン画面下部の右にある[+]ボタンを押下して、ini-parserライブラリをインストールする。

  • Visual Studioの場合
    1. プロジェクトを開く。
    2. NuGetパッケージマネージャーを開く。
      • [ツール]メインメニュー - [NuGetパッケージマネージャー]を選択して、[ソリューションのNuGetパッケージの管理]を選択する。
      • または、ソリューションエクスプローラーでプロジェクトを右クリックして、コンテキストメニューから[NuGetパッケージの管理]を選択する。
    3. ini-parserライブラリを検索する。
      NuGetパッケージマネージャーの検索ボックスに ini-parser と入力して検索する。
    4. ini-parserライブラリのインストール
      検索結果からini-parserライブラリを選択して、[インストール]ボタンを押下する。
    5. インストールの確認ダイアログが表示されるので、[OK]ボタンを押下してインストールを完了する。
    6. 参照の確認
      インストールが完了した後、プロジェクトの参照にini-parserライブラリが追加されていることを確認する。

  • パッケージマネージャーコンソールからインストールする場合
    1. プロジェクトを開く。
    2. [表示]メインメニュー - [その他のウィンドウ] - [パッケージマネージャーコンソール]を選択して、パッケージマネージャーコンソールを開く。
    3. パッケージマネージャーコンソールから、ini-parserライブラリをダウンロードしてインストールする。
      Install-Package ini-parser
    4. ソリューションエクスプローラーのプロジェクトの参照において、ini-parserライブラリが追加されていることを確認する。

  • dotnetコマンドを使用する場合
    1. ターミナルを開く。
    2. プロジェクトのルートディレクトリに移動する。
    3. ini-parserライブラリをインストールする。
      最新の安定版をインストールする場合
      dotnet add package ini-parser

      バージョンを指定してインストールする場合
      dotnet add package ini-parser --version <バージョン>

    ※注意
    プロジェクトがGit等のバージョン管理システムを使用している場合、これらの変更がトラッキングされることを確認すること。
    プロジェクトを再ビルドして、新しく追加されたパッケージが正しく統合されていることを確認することを推奨する。


プロジェクトにおいて、ini-parserライブラリを使用する場合は、ソースコードファイルの先頭にusingステートメントを追加する。

 using IniParser;
 using IniParser.Model;


また、大きなサイズなINIファイルを扱う場合は、ストリーミング処理を使用することを推奨する。

INIファイルの読み込み

以下の例では、ini-parserライブラリを使用して、INIファイルを非同期で読み込みしている。

 using System;
 using System.IO;
 using System.Threading.Tasks;
 using IniParser;
 using IniParser.Model;
 
 public class IniParserReader
 {
    private readonly FileIniDataParser _parser;
    private IniData _iniData;
 
    public IniParserReader()
    {
       _parser = new FileIniDataParser();
    }
 
    // INIファイルを非同期で読み込む
    public async Task ReadIniFileAsync(string filePath)
    {
       try
       {
          using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
          using (var streamReader = new StreamReader(fileStream))
          {
             var fileContent = await streamReader.ReadToEndAsync();
             _iniData = _parser.Parser.Parse(fileContent);
          }
          Console.WriteLine("INIファイルの読み込みが完了");
       }
       catch (FileNotFoundException)
       {
          Console.WriteLine("指定されたファイルが存在しない");
       }
       catch (IOException ex)
       {
          Console.WriteLine($"ファイルの読み込み中にエラーが発生: {ex.Message}");
       }
       catch (Exception ex)
       {
          Console.WriteLine($"予期せぬエラーが発生: {ex.Message}");
       }
    }
 
    // 値を取得するメソッド
    public string GetValue(string section, string key)
    {
       return _iniData?[section]?[key];
    }
 
    // 読み込んだINIファイルの内容を表示するメソッド
    public void DisplayContent()
    {
       if (_iniData == null)
       {
          Console.WriteLine("INIファイルが読み込まれていない");
          return;
       }
 
       foreach (var section in _iniData.Sections)
       {
          Console.WriteLine($"[{section.SectionName}]");
          foreach (var key in section.Keys)
          {
             Console.WriteLine($"{key.KeyName}={key.Value}");
          }
          Console.WriteLine();
       }
    }
 }


上記のクラスを使用する場合は、以下に示すようにインスタンスを生成して、メソッドを実行する。

 var iniReader = new IniParserReader();
 
 // INIファイルの読み込み
 await iniReader.ReadIniFileAsync("sample.ini");
 
 // 値の取得
 string value = iniReader.GetValue("セクション名", "キー名");
 
 // 取得した値の表示
 iniReader.DisplayContent();


INIファイルの書き込み

以下の例では、INIファイルを非同期で書き込みするクラスを定義している。

FileStreamクラスでは、下記の設定を行っている。

  • 新しいファイルを作成 または 既存のファイルを上書き
  • 書き込み専用で開く
  • 他のプロセスからのアクセスを禁止
    他のプロセスとファイルを共有したい場合は、FileShare.Noneを変更する。
  • 4[KB] = 4096[byte]のバッファを使用
    一般的に、4096[byte]はファイルI/Oに適したサイズとされている。
    大きすぎるとメモリを無駄に使い、小さすぎると頻繁なI/O操作が必要になる。
  • 非同期に操作することを指定


 using System;
 using System.IO;
 using System.Threading.Tasks;
 using IniParser;
 using IniParser.Model;
 
 public class IniParserWriter
 {
    private readonly FileIniDataParser _parser;
    private IniData _iniData;
 
    public IniParserWriter()
    {
       _parser = new FileIniDataParser();
       _iniData = new IniData();
    }
 
    // INIファイルを非同期で書き込む
    public async Task WriteIniFileAsync(string filePath)
    {
       try
       {
          using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
          using (var streamWriter = new StreamWriter(fileStream))
          {
             var content = _parser.Parser.WriteToString(_iniData);
             await streamWriter.WriteAsync(content);
          }
          Console.WriteLine("INIファイルの書き込みが完了");
       }
       catch (UnauthorizedAccessException)
       {
          Console.WriteLine("ファイルへの書き込み権限がない");
       }
       catch (IOException ex)
       {
          Console.WriteLine($"ファイルの書き込み中にエラーが発生: {ex.Message}");
       }
       catch (Exception ex)
       {
          Console.WriteLine($"予期せぬエラーが発生: {ex.Message}");
       }
    }
 
    // 値を設定する
    public void SetValue(string section, string key, string value)
    {
       _iniData[section][key] = value;
    }
 
    // セクションを追加する
    public void AddSection(string section)
    {
       if (!_iniData.Sections.ContainsSection(section))
       {
          _iniData.Sections.AddSection(section);
       }
    }
 
    // セクションを削除する
    public void RemoveSection(string section)
    {
       _iniData.Sections.RemoveSection(section);
    }
 
    // キーを削除する
    public void RemoveKey(string section, string key)
    {
       _iniData[section].RemoveKey(key);
    }
 }


上記のクラスを使用する場合は、以下に示すようにインスタンスを生成して、メソッドを実行する。

 var iniWriter = new IniParserWriter();
 
 // セクションを追加する
 iniWriter.AddSection("新しいセクション");
 
 // セクション, キー, 値をセット
 iniWriter.SetValue("新しいセクション", "新しいキー", "新しい値");
 
 // INIファイルへ書き込む
 await iniWriter.WriteIniFileAsync("sample.ini");