読者です 読者をやめる 読者になる 読者になる

SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

C#でiniファイルの読み書き

C#

ちょっと必要に迫られたのでやってみました。
f:id:minami_SC:20150312011730p:plain

.netのアプリ開発では、基本的にiniファイルの使用は推奨されてません。
なので、C#でコードを書いてるとiniファイルなんてほとんど使いません。

でも、今でもiniファイルを読み込みたいってことも極まれにありますよね。
昔作られたiniファイルにアクセスする必要がある場合とか。。。

てことで、C#からiniファイルを読み込んでみました。

参考サイト

以下ページを参考にやってみました。
↓のサイトでは、セクション内のキーの構造と一致するクラスを作り、リフレクションを用いてクラス内のすべての項目に対応するパース処理を一気に行ってました。これなら大量の要素があるiniファイルのパースも簡単にできますね。スバラシイ!!
C# で ini ファイルの内容を任意のクラスに格納するコードを書いてみた。 | みむらの手記手帳

上記サイトでは、Read/Writeというstaticメソッドを用意してパースしてます。
ここではstaticメソッドではなく、ジェネリックな拡張メソッドとして、任意の型にINIファイルの読み取り/書き込み用のメソッドを追加してみました。

IniFileHelperというクラスを作り、「using IniFileHelper;」というusing文を書くと、任意の型に対してParseFromIni/ExportToIniというIniファイルアクセス用の拡張メソッドが呼べるようになります。
f:id:minami_SC:20150312011713p:plain

IniFileHelper.cs
namespace IniFileHelper
{
    static class IniFileHelper
    {
        [DllImport("KERNEL32.DLL")]
        public static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, uint nSize, string lpFileName);

        [DllImport("KERNEL32.DLL")]
        public static extern uint GetPrivateProfileInt(string lpAppName, string lpKeyName, int nDefault, string lpFileName);

        [DllImport("KERNEL32.DLL")]
        public static extern uint WritePrivateProfileString(string lpAppName, string lpKeyName, string lpString, string lpFileName);
 
        public static void ParseFromIni<T>(this T self, string section, string filepath)
        {
            foreach(var prop in typeof(T).GetProperties())
            {
                if (prop.PropertyType == typeof(int))
                {
                    prop.SetValue(self, (int)GetPrivateProfileInt(section, prop.Name, 0, Path.GetFullPath(filepath)));
                }
                else if(prop.PropertyType == typeof(uint))
                {
                    prop.SetValue(self, GetPrivateProfileInt(section, prop.Name, 0, Path.GetFullPath(filepath)));
                }
                else
                {
                    var sb = new StringBuilder(1024);
                    GetPrivateProfileString(section, prop.Name, string.Empty, sb, (uint)sb.Capacity, Path.GetFullPath(filepath));
                    prop.SetValue(self, sb.ToString());
                }
            }
        }

        public static void ExportToIni<T>(this T self, string section, string filepath)
        {
            foreach(var prop in typeof(T).GetProperties())
            {
                WritePrivateProfileString(section, prop.Name, prop.GetValue(self).ToString(), Path.GetFullPath(filepath));
            }
        }
    }
}
Program.cs
    class Program
    {
        static void Main(string[] args)
        {
            var obj = new Section1();
            obj.ParseFromIni("Section1", @"H:\Test\sample.ini");
            Console.WriteLine(obj.Key1);
            Console.WriteLine(obj.Key2);
            Console.WriteLine(obj.Key3);
        }
    }

    class Section1
    {
        public string Key1 { get; set; }
        public string Key2 { get; set; }
        public string Key3 { get; set; }
    }
読み込み対象のiniファイル
[Section1]
Key1=sample
Key2=hogehoge
Key3=test
[Section2]
Key4=hello

特定の型のみ、拡張メソッドが現れるようにする

先ほどの方法では、「using IniFileHelper;」としてこの拡張メソッドを使える状態にすると、すべての型のインスタンスで、インテリセンスの候補として出てきてしまい、邪魔と感じるかもしれません。

そんな時は、空のマーカー用インターフェースを定義し、拡張メソッドジェネリック型制約としておくことで、特定の型のインスタンスのみ、この拡張メソッドが使えるようになります。

    // ジェネリック型制約のためのマーカーとして使用する、空のインターフェース
    interface ISupportIniFile
    {
    }

    // ・・・略
    // 拡張メソッドの型制約で、上記の空インターフェースを利用する
        public static void ParseFromIni<T>(this T self, string section, string filepath)
            where T : ISupportIniFile
        {
            foreach(var prop in typeof(T).GetProperties())
            {

INIファイルの読み書き対象としたいクラスは、以下のように上記のIFを実装しておけばOKです。

    class Section1 : ISupportIniFile
    {
        public string Key1 { get; set; }
        public string Key2 { get; set; }
        public string Key3 { get; set; }
    }

これで、インテリセンスの候補が汚れなくて済みますね♪

マーカー用途の空インターフェースについて

.netでは、基本的に空のインターフェース定義は推奨していません。
以下ページ参照
CA1040: 空のインターフェイスは使用しないでください

マーカー用途では、インターフェースではなくカスタム属性を作る、というのが.netのお作法です。
しかし、カスタム属性ではジェネリックメソッドの型制約はかけられないので、今回の目的には使えません。
また、先ほどのページでも、以下のような記述があるので、今回の用途はこのパターンに一応入るのかなぁ。。

コンパイル時に識別処理が発生する場合は、空のインターフェイスを使用できます。