C# で UTF-8 INI 読み書き(Linux も対応)
.NET Framework と Mono にて共通の UTF-8 限定 ini 読み書きクラス。
その昔我が SeeMe というアプリ用途に作ったクラスをなんとなく改良。
自分でまた使うかもしれないし、どうしても書き直したくなっただけ。
セクションの順番を保持するという特徴は変わっていない。
reference は mscorlib のみなので扱いも簡単です。
2011-05-05 まるごと作り替え
その昔我が SeeMe というアプリ用途に作ったクラスをなんとなく改良。
自分でまた使うかもしれないし、どうしても書き直したくなっただけ。
セクションの順番を保持するという特徴は変わっていない。
reference は mscorlib のみなので扱いも簡単です。
2011-05-05 まるごと作り替え
内容
ini file Read and Write (ConfigParser or Own class module)
にて Python で作ったのと同じようになる感じに変更した。
共通メソッドはベースクラスに作って派生させる方向で。
以前は Environment.NewLine で OS デフォルト改行コードになるのを忘れていた。
全部 summary を書いた、Visual Studio なら利用時にヒントが出るはず。
メソッドの命名規則は C# 推奨に従っているつもり。
Python 製同様に記述方法を間違えた ini を読み込んだ場合に例外を投げるのみに変更。
しかし動的言語の Python と違い書き込みはコンパイルエラーになるので読み込みのみチェック。
ただ Windows で UTF-8 を扱う場合は Byte Order Mark(BOM) の問題がある。
なので Bom プロパティを追加している、true にすると BOM 付きになる。
それ以外は Python 製と機能は同じである。
細かいことは summary でチェックしてね。
にて Python で作ったのと同じようになる感じに変更した。
共通メソッドはベースクラスに作って派生させる方向で。
以前は Environment.NewLine で OS デフォルト改行コードになるのを忘れていた。
全部 summary を書いた、Visual Studio なら利用時にヒントが出るはず。
メソッドの命名規則は C# 推奨に従っているつもり。
Python 製同様に記述方法を間違えた ini を読み込んだ場合に例外を投げるのみに変更。
しかし動的言語の Python と違い書き込みはコンパイルエラーになるので読み込みのみチェック。
ただ Windows で UTF-8 を扱う場合は Byte Order Mark(BOM) の問題がある。
なので Bom プロパティを追加している、true にすると BOM 付きになる。
それ以外は Python 製と機能は同じである。
細かいことは summary でチェックしてね。
コード
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
namespace IniFile8
{
/// <summary>
/// Ini ファイル読み書きのベースクラス
/// </summary>
public abstract class IniFileBase
{
// 例外用の定数、書き込みミスの場合はコンパイルエラーになるから不要
const string ERROR_READ = "Configuration file READ ERROR\n[{0}]\n{1}=\nvalue is not {2}";
/// <summary>
/// セクションとキーを指定して値を文字列で戻すメソッドベース
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
protected virtual string GetValue(string section, string key)
{
return "";
}
/// <summary>
/// Ini ファイルから整数値を読み取る
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <param name="defaultValue">キーが見つからない場合に戻すデフォルト値</param>
public int ReadInteger(string section, string key, int defaultValue)
{
string result = GetValue(section, key);
if (result == null)
return defaultValue;
try
{
return int.Parse(result);
}
catch(FormatException)
{
string s = String.Format(ERROR_READ, section, key, "int");
var ex = new FormatException(s);
throw ex;
}
}
/// <summary>
/// Ini ファイルから浮動小数点数値を読み取る
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <param name="defaultValue">キーが見つからない場合に戻すデフォルト値</param>
public float ReadFloat(string section, string key, float defaultValue)
{
string result = GetValue(section, key);
if (result == null)
return defaultValue;
try
{
return float.Parse(result);
}
catch (FormatException)
{
string s = String.Format(ERROR_READ, section, key, "float");
var ex = new FormatException(s);
throw ex;
}
}
/// <summary>
/// Ini ファイルから真偽(しんぎ)値を読み取る
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <param name="defaultValue">キーが見つからない場合に戻すデフォルト値</param>
public bool ReadBool(string section, string key, bool defaultValue)
{
string result = GetValue(section, key);
if (result == null)
return defaultValue;
if (result == "1")
return true;
else if (result == "0")
return false;
else
{
string s = String.Format(ERROR_READ, section, key, "bool");
var ex = new FormatException(s);
throw ex;
}
}
/// <summary>
/// Ini ファイルから文字列を読み取る
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <param name="defaultValue">キーが見つからない場合に戻すデフォルト値</param>
public string ReadString(string section, string key, string defaultValue)
{
string result = GetValue(section, key);
if (result == null)
return defaultValue;
return result;
}
}
/// <summary>
/// Ini ファイル中の特定値を取り出す軽量な読み取り専用クラス
/// </summary>
public class IniFileReader : IniFileBase
{
private string _fileName;
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="fileName">読み込む INI ファイルのフルパス</param>
public IniFileReader(string fileName)
{
_fileName = fileName;
}
/// <summary>
/// セクションとキーを指定してストリームから値を文字列で戻すメソッド
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <returns>値の文字列</returns>
protected override string GetValue(string section, string key)
{
bool bInSection = false;
int len;
string buf;
using (StreamReader sr = new StreamReader(_fileName))
{
while ((buf = sr.ReadLine()) != null)
{
if (buf == "") continue;
if (buf[0] == ';') continue;
len = buf.Length;
if (bInSection)
{
int lPos = buf.IndexOf('=');
if (lPos > 0)
{
if (key == buf.Substring(0, lPos))
{
return buf.Substring(lPos + 1, len - lPos - 1);
}
}
if (len > 2 && buf[0] == '[' && buf[len - 1] == ']')
{
return null; //次のセクションが始まってしまったので終了
}
}
else
{
if (len > 2 && buf[0] == '[' && buf[len - 1] == ']')
{
if (section == buf.Substring(1, len - 2))
bInSection = true;
}
}
}
}
return null;
}
}
/// <summary>
/// Ini ファイルを読み書きするクラス
/// 保存時にセクションおよびキーの順番は保持される
/// </summary>
public class IniFile : IniFileBase
{
/// <summary>
/// Byte Order Mark の有無
/// </summary>
public bool Bom { get; set; }
/// <summary>
/// 保存時に先頭にコメントを入れる場合
/// </summary>
public string Header { get; set; }
List<Dictionary<string, List<Dictionary<string, string> > > > ini;
string _fileName;
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="fileName">読み込む INI ファイルのフルパス</param>
public IniFile(string fileName)
{
Bom = false;
Header = "";
_fileName = fileName;
ini = new List<Dictionary<string, List<Dictionary<string, string> > > >();
if (File.Exists(_fileName))
{
bool bInSection = false;
int len;
string buf;
string section = "";
using (StreamReader sr = new StreamReader(_fileName))
{
while ((buf = sr.ReadLine()) != null)
{
if (buf == "") continue;
if (buf[0] == ';') continue;
len = buf.Length;
if (bInSection)
{
int lPos = buf.IndexOf('=');
if (lPos > 0)
{
string t1 = buf.Substring(0, lPos);
string t2 = buf.Substring(lPos + 1, len - lPos - 1);
Add(section, t1, t2);
}
if (len > 2 && buf[0] == '[' && buf[len - 1] == ']')
{
section = buf.Substring(1, len - 2);
}
}
else
{
// 最初のセクションが始まるまでガン無視させる
if (len > 2 && buf[0] == '[' && buf[len - 1] == ']')
{
if (section == buf.Substring(1, len - 2))
bInSection = true;
}
}
}
}
}
}
/// <summary>
/// Ini オブジェクトに追加を行う内部メソッド
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <param name="value">値の文字列</param>
private void Add(string section, string key, string value)
{
foreach (var dic1 in ini)
{
if (dic1.ContainsKey(section))
{
foreach (var dic2 in dic1[section])
{
if (dic2.ContainsKey(key))
{
dic2[key] = value;
return;
}
}
var dic3 = new Dictionary<string, string>();
dic3.Add(key, value);
dic1[section].Add(dic3);
return;
}
}
var dic4 = new Dictionary<string, string>();
dic4.Add(key, value);
var l1 = new List<Dictionary<string, string> >();
l1.Add(dic4);
var dic5 = new Dictionary<string, List<Dictionary<string, string> > >();
dic5.Add(section, l1);
ini.Add(dic5);
}
/// <summary>
/// セクションとキーを指定して Ini オブジェクトから値を文字列で戻すメソッド
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <returns>値の文字列</returns>
protected override string GetValue(string section, string key)
{
foreach (var dic1 in ini)
{
if (dic1.ContainsKey(section))
{
foreach (var dic2 in dic1[section])
{
if (dic2.ContainsKey(key))
{
return dic2[key];
}
}
}
}
return null;
}
/// <summary>
/// Ini オブジェクトをファイルに書き出すメソッド
/// </summary>
public void Save()
{
string crlf = Environment.NewLine; // Linux は LF になる
StreamWriter sw = null;
try
{
if (this.Bom)
sw = new StreamWriter(_fileName, false, Encoding.UTF8);
else
sw = new StreamWriter(_fileName, false);
if (this.Header != "")
sw.Write(this.Header);
foreach (var d1 in ini)
{
foreach (string section in d1.Keys)
{
sw.Write(String.Format("[{0}]{1}", section, crlf));
foreach (var d2 in d1[section])
{
foreach (string key in d2.Keys)
{
sw.Write(String.Format("{0}={1}{2}", key, d2[key], crlf));
}
}
sw.Write(crlf);
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
if (sw != null)
sw.Close();
}
}
/// <summary>
/// セクションの存在確認
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <returns>存在する場合は true</returns>
public bool SectionExists(string section)
{
foreach (var dic1 in ini)
{
if (dic1.ContainsKey(section))
{
return true;
}
}
return false;
}
/// <summary>
/// セクションを削除する
/// </summary>
/// <param name="section">セクションの文字列</param>
public void EraseSection(string section)
{
foreach (var dic1 in ini)
{
if (dic1.ContainsKey(section))
{
dic1.Remove(section);
}
}
}
/// <summary>
/// Ini オブジェクト内容を書き換え、又は追加
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <param name="Value">整数値</param>
public void WriteInteger(string section, string key, int Value)
{
this.Add(section, key, Value.ToString());
}
/// <summary>
/// Ini オブジェクト内容を書き換え、又は追加
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <param name="Value">浮動小数点数値</param>
public void WriteFloat(string section, string key, float Value)
{
this.Add(section, key, Value.ToString());
}
// <summary>
/// Ini オブジェクト内容を書き換え、又は追加
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <param name="Value">真偽値</param>
public void WriteBool(string section, string key, bool Value)
{
if (Value)
this.Add(section, key, "1");
else
this.Add(section, key, "0");
}
// <summary>
/// Ini オブジェクト内容を書き換え、又は追加
/// </summary>
/// <param name="section">セクションの文字列</param>
/// <param name="key">キーの文字列</param>
/// <param name="Value">文字列</param>
public void WriteString(string section, string key, string Value)
{
this.Add(section, key, Value);
}
}
}
使い方
たとえばこんなクラスを作ってみます。

と利用できます。
ビルドバッチを含めて ZIP でまとめたものも置いておきます
using System;
using System.Collections.Generic;
using System.IO;
using IniFile8;
namespace initest
{
class TestMain
{
[STAThread]
public static void Main(string[] args)
{
// EXE 位置に test.ini 作成
var ass = System.Reflection.Assembly.GetEntryAssembly();
string path = Path.Combine(Path.GetDirectoryName(ass.Location), "test.ini");
// IniFile オブジェクト
var ini = new IniFile8.IniFile(path);
try
{
ini.WriteString("せくしょん", "きー", "値");
ini.WriteString("二段目", "きー", "値");
ini.WriteInteger("せくしょん", "int", 666);
// 書き換えテスト
ini.WriteInteger("せくしょん", "int", 4);
// Byte Order Mark をつけるなら以下コメントアウトを外す
//ini.Bom = true;
ini.Save();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
// 読み込み例外テスト
var err = new IniFile8.IniFileReader(path);
try
{
int r = err.ReadInteger("せくしょん", "きー", 10);
Console.WriteLine(r);
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
Linux でビルドしてみます。$ gmcs -out:exex.exe *.cs $ ./exex.exe

と利用できます。
ビルドバッチを含めて ZIP でまとめたものも置いておきます
Copyright(C) sasakima-nao All rights reserved 2002 --- 2025.