3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C#でINIファイル操作を属性(Attribute)使ってやってみた

Last updated at Posted at 2021-01-05

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
}

最後までお付き合いいただき、誠にありがとうございました。

3
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?