C#でINIファイル操作
はじめにのはじめに
2021年あけましておめでとうございます。
極力今年はこまめにコードを書いていきたく、小ネタの集まりになりますがよろしくお願いいたします。
では2021年最初のネタです。
はじめに
- App.Configを利用し環境設定を保持するのが普通?の使い方と思われます。
- ですがまだまだ以前通りの使い方をしたいユーザのニーズがありなかなかXML環境に移行するのは・・・が実情です。
- それと多くのユーザが使っていると、なぜか一部のユーザからApp.Configを参照できないケースの報告があり(記述にヌケがある?)、個人的には枯れた技術であるINIを使うケースもあります。
使い方
- 単純にWindowsAPIを使います。
- 以下のコードをクラス内で定義して呼び出すだけです。
定義は以下の通り
- ※対象を文字列のみにしています。
- 私の使い方は数値を扱うAPIは使わず、文字列を読み取ってコード側で変換しています。(深い理由もないですが。。。)
#region INIファイル操作
/// <summary>
/// INIファイルから文字列の取得
/// </summary>
/// <param name="lpAppName">アプリケーション名</param>
/// <param name="lpKeyName">キー</param>
/// <param name="lpDefault">デフォルト値</param>
/// <param name="lpReturnedString">戻り値</param>
/// <param name="nSize">最大文字数</param>
/// <param name="lpFileName">ファイル名</param>
/// <returns>読み取りバイト数</returns>
[DllImport("KERNEL32.DLL")]
public static extern uint GetPrivateProfileString(
string lpAppName,
string lpKeyName,
string lpDefault,
StringBuilder lpReturnedString,
uint nSize,
string lpFileName);
/// <summary>
/// INIファイルへ文字列の書き込み
/// </summary>
/// <param name="lpAppName">アプリケーション名</param>
/// <param name="lpKeyName">キー</param>
/// <param name="lpString">書き込み文字数</param>
/// <param name="lpFileName">ファイル名</param>
/// <returns></returns>
[DllImport("KERNEL32.DLL")]
public static extern uint WritePrivateProfileString(
string lpAppName,
string lpKeyName,
string lpString,
string lpFileName);
#endregion
使い方
以下のように使います。
string appName = @"アプリケーション名";
string keyName = @"キー名";
string defaultValue = @"戻り値のデフォルト";
StringBuilder sb = new StringBuilder(256); // 戻り値
string iniFileName = Path.Combine(Application.StartupPath,"SETTING.INI");
uint res = GetPrivateProfileString(appName, keyName, defaultValue, sb, 256, iniFileName);
SETTING.INIの記述
[アプリケーション名]
キー名=値
- 結果、
resに"値"のバイト数が入り、sb.ToString()
は値
を返します。
この使い方は
- 正直に言うとこのままでは、値が増えるたびにコードを追加し続ける必要があるので面倒です。
- 同じコードを繰り返すのもなんだか・・・ですね。
属性を使ってコントロールする
- C#にはせっかくAttributeがあるので、活用します。
サンプルのINIファイルは以下の通りとします。
- ファイルのパスはテスト用の実行ファイルと同じフォルダーにあるものとします。
SETTING.INI
[app1]
data1=データ1
data2=データ2
[app2]
data3=データ3
- INIのデータを受け取るクラスを作ります。
/// <summary>
/// 設定保存用クラス
/// </summary>
public class SettingClass
{
/// <summary>
/// データ(アプリケーション名:app1 / キー名:data1)
/// </summary>
public string data1 { get; set; }
/// <summary>
/// データ(アプリケーション名:app1 / キー名:data2)
/// </summary>
public string data2 { get; set; }
/// <summary>
/// データ(アプリケーション名:app2 / キー名:data3 / デフォルト値:def3)
/// </summary>
public string data3 { get; set; }
}
-
クラスとファイル名を関連付けしてみます。
-
まずは属性用のクラスを作成します。
/// <summary>
/// INIファイル名属性
/// </summary>
public class IniFileNameAttribute
{
/// <summary>
/// ファイル名
/// </summary>
public string FileName { get; set; }
}
- 当然ですがこのままでは属性として認識されません。
-
System.Attribute
を継承します。
/// <summary>
/// INIファイル名属性
/// </summary>
public class IniFileNameAttribute : System.Attribute
{
/// <summary>
/// ファイル名
/// </summary>
public string FileName { get; set; }
}
- これがクラスや構造体で使えると宣言します。
- 宣言自体も属性で付与します。
/// <summary>
/// INIファイル名属性
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)]
public class IniFileNameAttribute : System.Attribute
{
/// <summary>
/// ファイル名
/// </summary>
public string FileName { get; set; }
}
- INIのデータを受け取るクラスに属性を付与してみます。
/// <summary>
/// 設定保存用クラス
/// </summary>
[IniFileName(FileName = "SETTINGS.INI")]
public class SettingClass
{
{省略}
- 属性の名称とクラス名は
Attribute
の部分が省略されています。
引数名を書かなければならないため、ちょっと冗長な感じですね。。。
属性にコンストラクターをつけます。
/// <summary>
/// INIファイル名属性
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)]
public class IniFileNameAttribute : System.Attribute
{
/// <summary>
/// ファイル名
/// </summary>
public string FileName { get; set; }
/// <summary>
/// INIファイルに対するファイル名属性
/// </summary>
/// <param name="filename">ファイル名</param>
public IniFileNameAttribute(string filename)
{
FileName = filename;
}
}
データ受け取りクラスも以下のようになります。
/// <summary>
/// 設定保存用クラス
/// </summary>
[IniFileName("SETTINGS.INI")]
public class SettingClass
{
{省略}
気持ち、ですがすっきりしました。
では、要素にも属性をつけましょう。
- 要素は、プロパティやフィールドに付与できるよう属性をつけます。
- 与えるのは、アプリケーション名、キー名、デフォルト値です。
- コンストラクターではアプリケーション名とキー名のみ強制し、デフォルト値を任意にします。
/// <summary>
/// INIファイルに対する属性値
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Property | AttributeTargets.Field)]
public class IniFileAttribute : System.Attribute
{
/// <summary>
/// アプリケーション名
/// </summary>
public string Application { get; set; }
/// <summary>
/// キー
/// </summary>
public string Key { get; set; }
/// <summary>
/// デフォルト値
/// </summary>
public string DefaultValue { get; set; }
/// <summary>
/// INIに対する連携用属性
/// </summary>
/// <param name="application">アプリケーション名</param>
/// <param name="key">キー</param>
public IniFileAttribute(string application, string key)
{
Application = application;
Key = key;
DefaultValue = string.Empty;
}
}
属性を付与した形は以下になります。
/// <summary>
/// 設定保存用クラス
/// </summary>
[IniFileName("SETTINGS.INI")]
public class SettingClass
{
/// <summary>
/// データ(アプリケーション名:app1 / キー名:data1)
/// </summary>
[IniFile("app1","data1")]
public string data1 { get; set; }
/// <summary>
/// データ(アプリケーション名:app1 / キー名:data2)
/// </summary>
[IniFile("app1","data2")]
public string data2 { get; set; }
/// <summary>
/// データ(アプリケーション名:app2 / キー名:data3 / デフォルト値:def3)
/// </summary>
[IniFile("app2","data3", DefaultValue="def3")]
public string data3 { get; set; }
}
こうなると、コメントは冗長な気がしてきます。
/// <summary>
/// 設定保存用クラス
/// </summary>
[IniFileName("SETTINGS.INI")]
public class SettingClass
{
/// <summary>
/// データ1
/// </summary>
[IniFile("app1","data1")]
public string data1 { get; set; }
/// <summary>
/// データ2
/// </summary>
[IniFile("app1","data2")]
public string data2 { get; set; }
/// <summary>
/// データ3
/// </summary>
[IniFile("app2","data3", DefaultValue="def3")]
public string data3 { get; set; }
}
すっきりしました。
では、肝心な読み取り部分を作成します。
クラスが持つ属性の取得方法
- Attributeの値を取得するには、以下の手順で取得します。
// まずは型を取得
Type t = typeof(SettingClass);
// 型が持つ属性を(この場合はIniFileAttribute属性)を取得
var att = (IniFileNameAttribute) Attribute.GetCustomAttribute(t, typeof(IniFileNameAttribute));
// 取得できなければ処理を抜ける
if (att == null) return null;
// 属性値、FileNameを取得
string iniFileName = att.FileName;
プロパティが持つ属性の取得方法
// まずは型を取得
Type t = typeof(SettingClass);
// まずは型のプロパティを全て取得
var prof = t.GetProperties();
// 全て取得したプロパティをひとつづつチェックする
foreach (var prop in prof)
{
// ----->>> 属性の有無をチェック
var att2 = (IniFileAttribute) prop.GetCustomAttribute(typeof(IniFileAttribute));
// ----->>> 属性がなければ無視
if (att2 == null) continue;
// ----->>> 属性からキーを得る
string appName = att2.Application;
string keyName = att2.Key;
string defaultValue = att2.DefaultValue;
// ----->>> 戻り値の準備
StringBuilder sb = new StringBuilder(256);
// ----->>> INIから取得
uint res = GetPrivateProfileString(appName, keyName, defaultValue, sb, 256, iniFileName);
}
いかがですか?
クラスのプロパティに付与された属性の取得方法、意外と簡単ではないでしょうか。
後は、取得したプロパティの実体にデータを流し込むだけです。
- 以下は、
result
変数に対して、戻り値を設定しています。 - サンプルは文字列と日付型のみを扱ってみています。
SettingClass result = new SettingClass();
// 文字列型のプロパティの場合
if (prop.PropertyType == typeof(string))
{
// ----->>> 文字列の場合
prop.SetValue(result, sb.ToString());
}
// 日付型のプロパティの場合
else if (prop.PropertyType == typeof(DateTime))
{
// ----->>> 日付型の場合
if (DateTime.TryParse(sb.ToString(), out DateTime dmy))
prop.SetValue(result, dmy);
}
データを受け付けるクラスが肥大化しても、読み込みの部分をいちいち増やす必要はありません。
(扱うプロパティの型を増やせば当然コードを増やさなければなりませんが)
-
GetPrivateProfileString
コードを1行増やせば簡単のに、なぜこんなに手間を増やさなきゃならないの?
とかいろいろご意見はあると思います。
今回はINIの話でしたが、属性を使えば相手がデータベースでも同様にできます。
(その場合はEntityFramework使えと怒られそうですが)
いろいろな使い方ができるので、属性を使ってみようかな、と思っていただけると幸いです。
完全な形は以下の通りとなります。
/// <summary>
/// INIファイル操作系
/// </summary>
public static class IniFileRWClass
{
#region INIファイル操作
/// <summary>
/// INIファイルから文字列の取得
/// </summary>
/// <param name="lpAppName">アプリケーション名</param>
/// <param name="lpKeyName">キー</param>
/// <param name="lpDefault">デフォルト値</param>
/// <param name="lpReturnedString">戻り値</param>
/// <param name="nSize">最大文字数</param>
/// <param name="lpFileName">ファイル名</param>
/// <returns>読み取りバイト数</returns>
[DllImport("KERNEL32.DLL")]
public static extern uint GetPrivateProfileString(
string lpAppName,
string lpKeyName,
string lpDefault,
StringBuilder lpReturnedString,
uint nSize,
string lpFileName);
/// <summary>
/// INIファイルへ文字列の書き込み
/// </summary>
/// <param name="lpAppName">アプリケーション名</param>
/// <param name="lpKeyName">キー</param>
/// <param name="lpString">書き込み文字数</param>
/// <param name="lpFileName">ファイル名</param>
/// <returns></returns>
[DllImport("KERNEL32.DLL")]
public static extern uint WritePrivateProfileString(
string lpAppName,
string lpKeyName,
string lpString,
string lpFileName);
#endregion
#region INI設定ファイル操作
/// <summary>
/// 設定をファイルから取り込む
/// </summary>
/// <returns></returns>
public static SettingClass ReadIniSetting()
{
//! ================================================================================
//! ----->>> 属性からINIファイル名を取得する(パスはEXEと同じフォルダにする)
SettingClass result = new SettingClass();
Type t = typeof(SettingClass);
var att = (IniFileNameAttribute) Attribute.GetCustomAttribute(t, typeof(IniFileNameAttribute));
if (att == null) return null;
string iniFileName = att.FileName;
string directry = Application.StartupPath;
iniFileName = Path.Combine(directry, iniFileName);
//! ================================================================================
//! ----->>> プロパティを検索し、属性値からアプリケーション名とキーを取得してINIファイルから値を得る
var prof = t.GetProperties();
foreach (var prop in prof)
{
//? ----->>> 属性の有無をチェック
var att2 = (IniFileAttribute) prop.GetCustomAttribute(typeof(IniFileAttribute));
if (att2 == null) continue;
//? ----->>> 属性からキーを得る
string appName = att2.Application;
string keyName = att2.Key;
string defaultValue = att2.DefaultValue;
//? ----->>> 戻り値の準備
StringBuilder sb = new StringBuilder(256);
//? ----->>> INIから取得
uint res = GetPrivateProfileString(appName, keyName, defaultValue, sb, 256, iniFileName);
if (prop.PropertyType == typeof(string))
{
//? ----->>> 文字列の場合
prop.SetValue(result, sb.ToString());
}
else if (prop.PropertyType == typeof(DateTime))
{
//? ----->>> 日付型の場合
if (DateTime.TryParse(sb.ToString(), out DateTime dmy))
{
prop.SetValue(result, dmy);
}
}
}
//? ================================================================================
//? ----->>> 戻り値を返す
return result;
}
/// <summary>
/// 設定をファイルに書き込む
/// </summary>
/// <param name="data"></param>
public static void WriteIniSetting(SettingClass data)
{
//! ================================================================================
//! ----->>> 属性からINIファイル名を取得する(パスはEXEと同じフォルダにする)
Type t = typeof(SettingClass);
var att = (IniFileNameAttribute)Attribute.GetCustomAttribute(t, typeof(IniFileNameAttribute));
if (att == null) return;
string iniFileName = att.FileName;
string directry = Application.StartupPath;
iniFileName = Path.Combine(directry, iniFileName);
//! ================================================================================
//! ----->>> プロパティを検索し、属性値からアプリケーション名とキーを取得してINIファイルへ値を書き込む
var prof = t.GetProperties();
foreach (var prop in prof)
{
var att2 = (IniFileAttribute)prop.GetCustomAttribute(typeof(IniFileAttribute));
if (att2 == null) continue;
//? ----->>> キーを取得
string appName = att2.Application;
string keyName = att2.Key;
//? ----->>> 値を文字列に変換する
string Value = prop.PropertyType == typeof(DateTime)
? ((DateTime) prop.GetValue(data)).ToString("yyyy/MM/dd")
: prop.GetValue(data).ToString();
//? ----->>> INIファイルに書き込む
WritePrivateProfileString(appName, keyName, Value, iniFileName);
}
}
#endregion
}
最後までお付き合いいただき、誠にありがとうございました。