C#で名前埋め込み型の文字列フォーマット操作

Python の文字列フォーマット操作では

sql = "SELECT %(name)s FROM %(table)s WHERE 10 < %(code)s" % \
    {"name":"C2", "table":"TBL1", "code":"C1" }

という風に、名前で変換する項目を指定出来ます。この方法、どんな値に置換されるか分りやすいので便利。

C# でも Python みたいに名前で指定したい!

そう思ったので、名前埋め込み型のフォーマット操作ができるメソッドをこしらえてみました。

public static class StringUtility
{
    // 辞書を渡す
    public static string NamedFormat(string format,
        IDictionary<string, object> args)
    {
        var sb = new StringBuilder(format);

        // 括弧をエスケープ
        sb.Replace("{{", "{{_");
        sb.Replace("}}", "_}}");

        // string.Format に渡すフォーマット作成
        // ついでに string.Format に渡す配列も作成
        var values = args.Select((pair, index) =>
        {
            sb.Replace("{" + pair.Key + "}", "{" + index + "}");
            return pair.Value;
        }).ToArray();

        // エスケープしたものを戻す
        sb.Replace("{{_", "{{");
        sb.Replace("_}}", "}}");

        // 整形は string.Format にまかせる
        return string.Format(sb.ToString(), values);
    }

    // 匿名クラスのインスタンスを渡す
    public static string NamedFormat(string format, object obj)
    {
        if (string.IsNullOrEmpty(format) || obj == null)
        {
            return string.Format(format, obj);
        }

        // { プロパティ名, プロパティ値 } の辞書を作って渡す
        return NamedFormat(format,
            obj.GetType().GetProperties()
                .ToDictionary(p => p.Name, p => p.GetValue(obj, null)));
    }
}

使い方は次の通りです

class Program
{
    static void Main(string[] args)
    {
        // 辞書で指定
        var query1 = StringUtility.NamedFormat(@"
            SELECT {code},
                    {name}            
            FROM {tableName}
            WHERE {createdAt} = @createdAt
            ORDER BY {code}",
            new Dictionary<string, object>()
            {
                { "tableName", "TBL1" },
                { "code", "C1" },
                { "name", "C2" },
                { "createdAt", "C3" }
            });
        Console.WriteLine(query1);

        // 匿名クラスで指定
        var query2 = StringUtility.NamedFormat(@"
            SELECT {code},
                    {name}            
            FROM {tableName}
            WHERE {createdAt} = @createdAt
            ORDER BY {code}",
            new
            {
                tableName = "TBL1",
                code = "C1",
                name = "C2",
                createdAt = "C3"
            });
        Console.WriteLine(query2);

        // 匿名クラスで指定(プロパティ名=変数名)
        string tableName = "TBL1";
        string code = "C1";
        string name = "C2";
        string createdAt = "C3";
        var query3 = StringUtility.NamedFormat(@"
            SELECT {code},
                    {name}            
            FROM {tableName}
            WHERE {createdAt} = @createdAt
            ORDER BY {code}",
            new
            {
                tableName,
                code,
                name,
                createdAt
            });
        Console.WriteLine(query3);

        Console.ReadLine();
    }
}

3番目の例は、Ruby の変数評価文字列に似てる気がします。

実行すると

f:id:griefworker:20110407144909p:image
ちゃんとフォーマットされていますね。

SQL を書くときに一番効果を発揮しそうです

ただ、ADO.NET Entity Framework + LINQ で開発している場合は SQL を書くことは少ないですね。まぁ、いいか。