« 2013年10月 | メイン | 2013年12月 »

2013年11月 アーカイブ

2013年11月01日

[C#][.NET] メタプログラミング入門 - はじめに

Metasequoia

数回に渡って、C#/.NET によるメタプログラミングを紹介して行きたい。

先ずは概要から。

■ メタプログラミング

・メタとは

メタ (meta) は、「高次な-」「超-」等の意味の接頭語で、ギリシャ語から来ている。

プログラミングでは、メタプログラミングの他、メタクラス (=クラスがインスタンスとなるクラス)、メタデータ (データが持つそのデータ自身についての付加的なデータ) 等で用いられている。

・メタプログラミングとは

メタプログラミングは、「高次なプログラミング」ということで、「プログラムを操作したり出力したりするプログラムを書くこと」だ。

プログラムでプログラムを出力する (メタプログラミング) 方が、手でプログラムを書くよりも効率的な場合がある。

・DRY の原則とメタプログラミング

プログラミングでは、「DRY (Don't repeat yourself) の原則」というものが重視される。「繰り返しを避けるべし」というものだ。

オブジェクト指向プログラミングジェネリック プログラミング等では、抽象化を行うことで、或る程度コードの重複を避けることができる。

だが、「銀の弾などない」という言葉が示すように、オブジェクト指向を採用すれば、或いは、ジェネリック プログラミングを用いれば、それで常にうまく行く、という訳には行かない。何か一つのパラダイムで全てが解決する、というような特効薬などなく、状況に合った幾つかの方法を組み合わせて用いる必要がある。

メタプログラミングもそうしたものの一つだ。

例えば、似たようなプログラムが繰り返し必要な場合に、メタプログラミング、即ち、「プログラムを操作したり出力したりするプログラムを書くこと」で、プログラミングの手書きソースコードの重複や手書き作業の重複を避けられる場合がある。

・メタプログラミングが有効な例

メタプログラミングが有効な例として、次のようなものがある。

  • コンパイラー/インタープリター
    ホスト言語のソースコードから動的に対象言語のプログラムを生成
  • O/R マッパー
    クラスやオブジェクトから動的に SQL を生成
  • XML や JSON の入出力
    クラスやオブジェクト等から動的に XMLJSON を生成/XML や JSON から動的にクラスやオブジェクト等を生成
    (生成するプログラムをプログラムで生成)
  • モック (mock) オブジェクト
    モック (ユニットテストで用いられる代用のオブジェクト) を動的に生成
    (生成するプログラムをプログラムで生成)
  • Web アプリケーション
    クライアント側で動作するプログラム (HTMLJavaScript 等) をサーバー側で動的に生成

■ C#/.NET におけるメタプログラミング

C#/.NET で、「プログラムを操作したり出力したりするプログラムを書く」為には、次のような技術が用意されている。

  • リフレクション
    .NET の System.Reflection 名前空間の中の型を用いて、アセンブリやクラス、クラスのメンバーやインスタンスに関する情報 (メタデータ) を取得したり、メンバーを呼び出したりすることができる。
    リフレクションについては、以前に何度か紹介した。
  • リフレクションの Emit
    System.Reflection.Emit 名前空間の中の型を用いると、CIL (Common Intermediate Language: 共通中間言語) を生成することで、クラスやクラスのメンバーを動的に生成することができる。
  • 式木
    System.Linq.Expressions 名前空間の型を用いて、式木を生成し、動的にプログラムを生成することができる。
  • Roslyn
    以前、「Roslyn による Visual Studio のアドイン」で紹介した Roslyn は、C# や Visual Basic のコンパイラーの内部の API 等を公開したものだ。
    本稿執筆時点では CTP (Community Technology Preview) と呼ばれる評価版だが、これを用いて、C# のソースコードから、プログラムを生成することができる。

これらの他に、Visual StudioT4 (Text Template Transformation Toolkit) Template という機能を使って、コードを生成させる方法もある。

次回から、これらの技術について、紹介して行こうと思う。

2013年11月02日

[C#][.NET] メタプログラミング入門 - Reflection.Emit による Add メソッドの動的生成

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 - はじめに」の続き。

Reflection.Emit によるメタプログラミング

前回は、C#/.NET でメタプログラミングを行う方法について述べた。

これから数回に渡って、それぞれの方法について紹介していきたい。

今回は、Reflection.Emit によるメソッドの動的生成だ。

動的に生成するメソッド

簡単な例として int の足し算を行うだけのメソッドを作ってみたい。

次のようなものだ。

    // 普通の静的な Add メソッド
    static int Add(int x, int y)
    {
        return x + y;
    }

Reflection.Emit では、CIL (Common Intermediate Language: 共通中間言語) を生成して、メソッド等を作成することができる。

先ずは、この Add メソッドの IL (Intermediate Language) がどのようなものかをツールを使って見てみよう。

ILSpy を使って IL を見る

アセンブリの IL は、.NET ReflectorILSpy といったツールを使うことで見ることができる。

ILSpy は、無償で SourceForge.net の ILSpy - SharpDevelop からダウンロードして使うことができる。

例えば、次のようなコンソール アプリをビルドし、出来上がったアセンブリを ILSpy.exe で開いてみよう。

static class Program
{
    // 普通の静的な Add メソッド
    static int Add(int x, int y)
    {
        return x + y;
    }
    
    static void Main()
    {}
}
ILSpy で Add メソッドの IL を見る
ILSpy で Add メソッドの IL を見る

これを Reflection.Emit を用いて生成してみよう。

Reflection.Emit による Add メソッドの動的生成

実際にやってみると次のようになる。

using System;
using System.Reflection;
using System.Reflection.Emit;

static class Program
{
    // Reflection.Emit の DynamicMethod による Add メソッドの生成
    static Func<int, int, int> AddByEmit()
    {
        // DynamicMethod
        var method = new DynamicMethod(
            name          : "add"                            ,
            returnType    : typeof(int)                      ,
            parameterTypes: new[] { typeof(int), typeof(int)}
        );

        // 引数 x 生成用
        var x = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "x");
        // 引数 y 生成用
        var y = method.DefineParameter(position: 2, attributes: ParameterAttributes.In, parameterName: "y");
        // ILGenerator
        var generator = method.GetILGenerator();

        // 生成したい IL
        // IL_0000: ldarg.0
        // IL_0001: ldarg.1
        // IL_0002: add
        // IL_0003: ret

        // 「最初の引数をスタックにプッシュする」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_0);
        // 「二つ目の引数をスタックにプッシュ」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_1);
        // 「二つの値を加算する」コードを生成
        generator.Emit(opcode: OpCodes.Add    );
        // 「リターンする」コードを生成
        generator.Emit(opcode: OpCodes.Ret    );

        // 動的にデリゲートを生成
        return (Func<int, int, int>)method.CreateDelegate(delegateType: typeof(Func<int, int, int>));
    }

    static void Main()
    {
        var addByEmit    = AddByEmit();     // デリゲートを動的に生成
        var answerByEmit = addByEmit(1, 2); // 生成したデリゲートの呼び出し
        Console.WriteLine("answerByEmit: {0}", answerByEmit);
    }
}

実行してみると、次のように正しく動作するのが分かるだろう。

answerByEmit: 3

まとめ

今回は、Reflection.Emit を用いて、動的にメソッドを生成するプログラムを作成した。

次回は、他の方法も試してみよう。

2013年11月03日

[C#][.NET][式木] メタプログラミング入門 - 式木による Add メソッドの動的生成

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 - Reflection.Emit による Add メソッドの動的生成」の続き。

式木によるメタプログラミング

前回は、Reflection.Emit を用いて Add メソッドを動的生成するプログラムを作成した。

今回は、式木によるメソッドの動的生成だ。

動的に生成するメソッド

今回も次の Add メソッドを生成する。

    // 普通の静的な Add メソッド
    static int Add(int x, int y)
    {
        return x + y;
    }

前回は、ILSpy で IL を調べ、それを参考にしたた。

今回は式木として生成するため、先ずは Add メソッドにあたる式を作り、その構造を見て参考にしよう。

以前、「Expression の構造を調べてみる」で行ったように、

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        Expression<Func<int, int, int>> add = (x, y) => x + y;
    }
}

のようなプログラムを作成し、デバッグ実行で、add の構造を調べてみよう。

Visual Studio のデバッガーの「クイックウォッチ」の表示から一部抜粋

Visual Studio のデバッガーで add 式の構造を見る (一部抜粋)
Visual Studio のデバッガーで add 式の構造を見る (一部抜粋)

これを見ると、次のような構造をしていることが分かる。

add 式の構造
add 式の構造

これを式木を用いて生成してみよう。

式木による Add メソッドの動的生成

実際にやってみると次のようになる。

using System;
using System.Linq.Expressions;

static class Program
{
    // Expression (式) による Add メソッドの生成
    static Func<int, int, int> AddByExpression()
    {
        // 生成したい式
        // (int x, int y) => x + y

        var x      = Expression.Parameter(type: typeof(int)); // 引数 x の式
        var y      = Expression.Parameter(type: typeof(int)); // 引数 y の式
        var add    = Expression.Add      (left: x, right: y); // x + y の式
        var lambda = Expression.Lambda   (add, x, y        ); // (x, y) => x + y の式
        // ラムダ式をコンパイルしてデリゲートとして返す
        return (Func<int, int, int>)lambda.Compile();
    }

    static void Main()
    {
        var addByExpression    = AddByExpression();     // デリゲートを動的に生成
        var answerByExpression = addByExpression(1, 2); // 生成したデリゲートの呼び出し
        Console.WriteLine("answerByExpression: {0}", answerByExpression);
    }
}

Reflection.Emit を使った場合と比較すると、やや簡潔に書けるのが分かるだろう。

実行してみると、次のように正しく動作する。

answerByExpression: 3

まとめ

今回は、式木を用いて、動的にメソッドを生成するプログラムを作成した。

次回は、更に他の方法も試してみよう。

2013年11月04日

[C#][.NET][Roslyn] メタプログラミング入門 - Roslyn による Add メソッドの動的生成

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 - 式木による Add メソッドの動的生成」の続き。

Roslyn によるメタプログラミング

前回は、式木を用いて Add メソッドを動的生成するプログラムを作成した。

今回は、Roslyn によるメソッドの動的生成だ。

Roslyn は、「Roslyn による Visual Studio のアドイン」で紹介したが、C# や Visual Basic のコンパイラーの内部の API 等を公開したものだ。

本稿執筆時点では CTP (Community Technology Preview) と呼ばれる評価版だが、これを用いて、C# のソースコードから、プログラムを生成することができる。

Roslyn のインストール

先ずは、Roslyn をインストールしよう。

Visual Studio の「ソリューション エクスプローラー」でプロジェクト名を右クリックし、「Nuget パッケージの管理...」を選ぶ。

Visual Studio の「Nuget パッケージの管理...」メニュー
Visual Studio の「Nuget パッケージの管理...」メニュー

「Nuget パッケージの管理」ダイアログボックスが開くので、右上の「オンラインの検索」エディット ボックスに「Roslyn」と入力し、検索する。

Visual Studio の「Nuget パッケージの管理」ダイアログボックス
Visual Studio の「Nuget パッケージの管理」ダイアログボックス

暫く待って表示されたリストの中から「Roslyn」を選び、「インストール」する。

動的に生成するメソッド

今回も次の Add メソッドを生成する。

    // 普通の静的な Add メソッド
    static int Add(int x, int y)
    {
        return x + y;
    }

Roslyn を使うソースコードは次のようになる。

using Roslyn.Scripting.CSharp;
using System;

static class Program
{
    // Roslyn による Add メソッドの生成
    static Func<int, int, int> AddByRoslyn()
    {
        var engine  = new ScriptEngine(); // C# のスクリプトエンジン
        var session = engine.CreateSession();
        session.ImportNamespace("System"); // System 名前空間のインポート

        return (Func<int, int, int>)session.Execute(code: "(Func<int, int, int>)((x, y) => x + y)");
    }
    
    static void Main()
    {
        var addByRoslyn    = AddByRoslyn();     // デリゲートを動的に生成
        var answerByRoslyn = addByRoslyn(1, 2); // 生成したメソッドの呼び出し
        Console.WriteLine("answerByRoslyn: {0}", answerByRoslyn);
    }
}

C# のソースコードを直接扱えるので、Reflection.Emit を使った場合式木を使った場合と 比較して、とても簡潔に書ける。

実行してみると、これも次のように正しく動作する。

answerByRoslyn: 3

まとめ

今回は、Roslyn を用いて、動的にメソッドを生成するプログラムを作成した。

これで三通りの方法を試したことになる。次回は、これらのパフォーマンスを比較してみよう。

2013年11月05日

[C#][.NET] メタプログラミング入門 - Add メソッドのパフォーマンスの比較

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 - Roslyn による Add メソッドの動的生成」の続き。

C# によるメタプログラミングでのパフォーマンスの比較

前回まで、C# によるメタプログラミングで Add メソッドを動的生成するプログラムを作成してきた。

今回は、それぞれの手法における実行速度を測ってみよう。

実行に掛かった時間の測定用のクラス

それぞれの実行に掛かった時間を測る為、次のようなクラスを用意することにした。

using System;
using System.Diagnostics;
using System.Linq.Expressions;

public static class パフォーマンステスター
{
    public static void テスト(Expression<Action> 処理式, int 回数, Action<string> output)
    {
        // 処理でなく処理式として受け取っているのは、文字列として出力する為
        var 処理 = 処理式.Compile();
        var 時間 = 計測(処理, 回数).TotalMilliseconds; // 回数分の処理に掛かったミリ秒数
        // 一回当たり何秒掛かったかを出力
        output(string.Format("{0,70}: {1,10:F}/{2} 秒", 処理式.Body.ToString(), 時間, 回数 * 1000));
    }

    static TimeSpan 計測(Action 処理, int 回数)
    {
        var stopwatch = new Stopwatch(); // 時間計測用
        stopwatch.Start();
        回数.回(処理);
        stopwatch.Stop();
        return stopwatch.Elapsed;
    }

    static void 回(this int @this, Action 処理)
    {
        for (var カウンター = 0; カウンター < @this; カウンター++)
            処理();
    }
}

それでは実際に測ってみよう。

デリゲートの動的生成のパフォーマンスのテスト

先ずは、デリゲートの動的生成に掛かる時間。

これまでの三種類のコードを呼び出し、それぞれがデリゲートを作成するまでに掛かる時間を測ってみる。

using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

static class Program
{
    // Reflection.Emit の DynamicMethod による Add メソッドの生成
    static Func<int, int, int> AddByEmit()
    {
        // DynamicMethod
        var method = new DynamicMethod(
            name          : "add",
            returnType    : typeof(int),
            parameterTypes: new[] { typeof(int), typeof(int) }
        );

        // 引数 x 生成用
        var x         = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "x");
        // 引数 y 生成用
        var y         = method.DefineParameter(position: 2, attributes: ParameterAttributes.In, parameterName: "y");
        // ILGenerator
        var generator = method.GetILGenerator();

        // 生成したい IL
        // IL_0000: ldarg.0
        // IL_0001: ldarg.1
        // IL_0002: add
        // IL_0003: ret

        // 「最初の引数をスタックにプッシュする」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_0);
        // 「二つ目の引数をスタックにプッシュ」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_1);
        // 「二つの値を加算する」コードを生成
        generator.Emit(opcode: OpCodes.Add    );
        // 「リターンする」コードを生成
        generator.Emit(opcode: OpCodes.Ret    );

        // 動的にデリゲートを生成
        return (Func<int, int, int>)method.CreateDelegate(delegateType: typeof(Func<int, int, int>));
    }

    // Expression (式) による Add メソッドの生成
    static Func<int, int, int> AddByExpression()
    {
        // 生成したい式
        // (int x, int y) => x + y

        var x      = Expression.Parameter(type: typeof(int)); // 引数 x の式
        var y      = Expression.Parameter(type: typeof(int)); // 引数 y の式
        var add    = Expression.Add      (left: x, right: y); // x + y の式
        var lambda = Expression.Lambda   (add, x, y        ); // (x, y) => x + y の式
        // ラムダ式をコンパイルしてデリゲートとして返す
        return (Func<int, int, int>)lambda.Compile();
    }

    // Roslyn による Add メソッドの生成
    static Func<int, int, int> AddByRoslyn()
    {
        var engine  = new ScriptEngine(); // C# のスクリプトエンジン
        var session = engine.CreateSession();
        session.ImportNamespace("System"); // System 名前空間のインポート

        return (Func<int, int, int>)session.Execute(code: "(Func<int, int, int>)((x, y) => x + y)");
    }

    static void Main()
    {
        生成のパフォーマンステスト();
    }

    static void 生成のパフォーマンステスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        const int 回数 = 1000;

        パフォーマンステスト(() => AddByEmit      (), 回数); // Reflectin.Emit による生成
        パフォーマンステスト(() => AddByExpression(), 回数); // 式木による生成
        パフォーマンステスト(() => AddByRoslyn    (), 回数); // Roslyn による生成
    }

    static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
    {
        パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
    }
}

実行してみよう。

【生成のパフォーマンステスト】
                                                           AddByEmit():       7.43/1000000 秒
                                                     AddByExpression():      77.82/1000000 秒
                                                         AddByRoslyn():    3088.51/1000000 秒

速い順に並べてみよう。

順位 方法 時間 (マイクロ秒)
1 Reflection.Emit を使って動的にメソッドを生成した場合 7.43
2 式木を使って動的にメソッドを生成した場合 77.82
3 Roslyn を使って動的にメソッドを生成した場合 3088.51

手間が掛からない方法程時間が掛かっているのが分かる。Roslyn は特に時間が掛かる。3088.51/1000000 秒ということは、約 0.003 秒も掛かっていることになる。

生成済みデリゲートの実行のパフォーマンスのテスト

次は、それぞれの動的生成済みのデリゲートを実行する時間だ。

今度のコードでは、デリゲートを動的生成する迄の時間は測らず、生成後のデリゲートの実行時間を測る。

using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

static class Program
{
    // 普通の静的な Add メソッド
    static int Add(int x, int y)
    {
        return x + y;
    }

    // Reflection.Emit の DynamicMethod による Add メソッドの生成
    static Func<int, int, int> AddByEmit()
    {
        // DynamicMethod
        var method = new DynamicMethod(
            name          : "add",
            returnType    : typeof(int),
            parameterTypes: new[] { typeof(int), typeof(int) }
        );

        // 引数 x 生成用
        var x         = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "x");
        // 引数 y 生成用
        var y         = method.DefineParameter(position: 2, attributes: ParameterAttributes.In, parameterName: "y");
        // ILGenerator
        var generator = method.GetILGenerator();

        // 生成したい IL
        // IL_0000: ldarg.0
        // IL_0001: ldarg.1
        // IL_0002: add
        // IL_0003: ret

        // 「最初の引数をスタックにプッシュする」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_0);
        // 「二つ目の引数をスタックにプッシュ」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_1);
        // 「二つの値を加算する」コードを生成
        generator.Emit(opcode: OpCodes.Add    );
        // 「リターンする」コードを生成
        generator.Emit(opcode: OpCodes.Ret    );

        // 動的にデリゲートを生成
        return (Func<int, int, int>)method.CreateDelegate(delegateType: typeof(Func<int, int, int>));
    }

    // Expression (式) による Add メソッドの生成
    static Func<int, int, int> AddByExpression()
    {
        var x      = Expression.Parameter(type: typeof(int)); // 引数 x の式
        var y      = Expression.Parameter(type: typeof(int)); // 引数 y の式
        var add    = Expression.Add      (left: x, right: y); // x + y の式
        var lambda = Expression.Lambda   (add, x, y        ); // (x, y) => x + y の式
        // ラムダ式をコンパイルしてデリゲートとして返す
        return (Func<int, int, int>)lambda.Compile();
    }

    // Roslyn による Add メソッドの生成
    static Func<int, int, int> AddByRoslyn()
    {
        var engine  = new ScriptEngine();
        var session = engine.CreateSession();
        session.ImportNamespace("System"); // System 名前空間のインポート

        return (Func<int, int, int>)session.Execute(code: "(Func<int, int, int>)((x, y) => x + y)");
    }

    static void Main()
    {
        実行のパフォーマンステスト();
    }

    static void 実行のパフォーマンステスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        // それぞれのデリゲートを準備
        Func<int, int, int> add             = Add              ;
        var                 addByEmit       = AddByEmit      (); // Reflectin.Emit による生成
        var                 addByExpression = AddByExpression(); // 式木による生成
        var                 addByRoslyn     = AddByRoslyn    (); // Roslyn による生成

        const int           回数          = 1000000;

        パフォーマンステスト(() => Add            (1, 2), 回数); // 静的な Add を直接呼ぶ
        パフォーマンステスト(() => add            (1, 2), 回数); // 静的な Add をデリゲートに入れて呼ぶ
        パフォーマンステスト(() => addByEmit      (1, 2), 回数); // Reflectin.Emit によって生成済みのデリゲートを呼ぶ
        パフォーマンステスト(() => addByExpression(1, 2), 回数); // 式木によって生成済みのデリゲートを呼ぶ
        パフォーマンステスト(() => addByRoslyn    (1, 2), 回数); // Roslyn によって生成済みのデリゲートを呼ぶ
    }

    static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
    {
        パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
    }
}

実行してみよう。

【実行のパフォーマンステスト】
                                                             Add(1, 2):      12.64/1000000000 秒
                   Invoke(value(Program+<>c__DisplayClass0).add, 1, 2):       6.72/1000000000 秒
               Invoke(value(Program+<>c__DisplayClass0).addEmit, 1, 2):       6.68/1000000000 秒
         Invoke(value(Program+<>c__DisplayClass0).addExpression, 1, 2):       4.45/1000000000 秒
             Invoke(value(Program+<>c__DisplayClass0).addRoslyn, 1, 2):       6.55/1000000000 秒

速い順に並べてみよう。

順位 方法 時間 (ナノ秒)
1 式木によって生成済みのデリゲートを呼ぶ 4.45
2 Roslyn によって生成済みのデリゲートを呼ぶ 6.55
3 Reflectin.Emit によって生成済みのデリゲートを呼ぶ 6.68
4 静的な Add をデリゲートに入れて呼ぶ 6.72
5 静的な Add を直接呼ぶ 12.64

今度は、どの結果も大差ない。毎回メソッドを直接呼ぶよりは寧ろ速いことが分かる。

まとめ

方法にも因るが、動的生成自体はそこそこハイコストであることが分かった。

従って、実行の都度、動的生成を行うのではなく、一度生成したデリゲートはキャッシュしておくのが有効だと思われる。

一度生成すれば、リフレクション等を用いた動的なコードとは異なり、通常のデリゲートなので、実行自体に時間が掛かる訳ではない。

次回からは、別のケースでキャッシュを用いた場合について検証していく。

2013年11月06日

[C#][.NET] メタプログラミング入門 - メソッド呼び出しのパフォーマンスの比較

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 - Add メソッドのパフォーマンスの比較」の続き。

C# によるメタプログラミングでのパフォーマンスの比較

前回は、C# によるメタプログラミングで Add メソッドを動的生成した場合の実行速度を測定した。

今回も同様に、メソッドを動的生成した場合の実行速度を測定しよう。今回は、メソッド呼び出しのパフォーマンスを検証する。

次の順番で進めていく。

  • それぞれのメソッド呼び出し方法の紹介
    • 通常の静的なメソッド呼び出し
    • リフレクションを使った動的なメソッド呼び出し
    • dynamic を使った動的なメソッド呼び出し
    • Reflection.Emit を使って動的にメソッドを生成した場合
    • 式木を使って動的にメソッドを生成した場合
    • Roslyn を使って動的にメソッドを生成した場合
  • 実行に掛かった時間の測定用のクラスの準備
  • デリゲートの動的生成のパフォーマンスのテスト
    • Reflectin.Emit による生成
    • 式木による生成
    • Roslyn による生成
  • 実行のパフォーマンステスト
    • デリゲートの動的生成を行わない場合
      • デリゲートでなくメソッドを直接呼ぶ場合
      • メソッドをデリゲートに入れてから呼ぶ場合
      • リフレクションによる動的呼び出し
      • dynamic による動的呼び出し
    • 生成済みのデリゲートを使って呼ぶ場合
      • Reflectin.Emit による生成
      • 式木による生成
      • Roslyn による生成
  • キャッシュによる実行のパフォーマンステスト
    • コード生成しない場合と、コード生成しつつ呼ぶ場合 コード生成にキャッシュを効かせた場合

それぞれのメソッド呼び出し方法

先ずは、それぞれの実際の呼び出しの例を示そう。

  • 通常の静的なメソッド呼び出し
  • リフレクションを使った動的なメソッド呼び出し
  • dynamic を使った動的なメソッド呼び出し
  • Reflection.Emit を使って動的にメソッドを生成した場合
  • 式木を使って動的にメソッドを生成した場合
  • Roslyn を使って動的にメソッドを生成した場合

先ず、試しに呼び出すメソッドを準備しよう。

// 検証用のクラス
public class Something
{
    // 検証用のメソッド
    public string DoSomething()
    {
        return "Hello!";
    }
}

この Something クラスのインスタンスの DoSomething() メソッドを呼び出すこととする。

順次、それぞれの方法での呼び出しを書いて行こう。

普通の静的なメソッド呼び出し
    // 普通の静的なメソッド呼び出し
    static string Call(Something item)
    {
        // 静的なメソッド呼び出し
        return item.DoSomething();
    }
リフレクションを使ったメソッド呼び出し

詳しくは以前の記事等を参考にしてほしい。

    // リフレクションを使ったメソッド呼び出し
    static object CallByReflection(object item)
    {
        // リフレクションを使って動的にメソッド情報を取得
        var doSomething = item.GetType().GetMethod("DoSomething");
        // メソッドの動的呼び出し
        return doSomething.Invoke(item, null);
    }
dynamic を使ったメソッド呼び出し

詳しくは以前の記事等を参考にしてほしい。

    // dynamic を使ったメソッド呼び出し
    static dynamic CallByDynamic(dynamic item)
    {
        // dynamic による動的なメソッド呼び出し
        return item.DoSomething();
    }
Reflection.Emit を使って動的にメソッドを生成した場合

[C#][.NET] メタプログラミング入門 - Reflection.Emit による Add メソッドの動的生成」でやったようにやってみよう。

生成したいコードを先ず C# で書いてビルドし、ILSpyIL (Intermediate Language) を調べてみよう。

次のような C# のソースコードをビルドし、

// 検証用のクラス
public class Something
{
    // 検証用のメソッド
    public string DoSomething()
    {
        return "Hello!";
    }
}

static class Program
{
    // 普通の静的なメソッド呼び出し
    static string Call(Something item)
    {
        return item.DoSomething();
    }

    static void Main()
    {}
}

Call メソッドの部分を ILSpy で見る。

ILSpy で Call メソッドの IL を見る
ILSpy で Call メソッドの IL を見る

参考にして、Reflection.Emit でメソッド生成を行ってみよう。

    // using namespace 省略

    // Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
    {
        // DynamicMethod
        var method = new DynamicMethod(
            name          : "call"             ,
            returnType    : typeof(string)     ,
            parameterTypes: new[] { typeof(T) }
        );

        // 引数 item 生成用
        var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
        // ILGenerator
        var generator = method.GetILGenerator();

        // 生成したい IL
        // IL_0000: ldarg.0
        // IL_0001: callvirt instance void Something::DoSomething()
        // IL_0006: ret

        // 「引数をスタックにプッシュする」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_0);
        // 「指定された名前のメソッドを呼ぶ」コードを生成
        generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
        // 「リターンする」コードを生成
        generator.Emit(opcode: OpCodes.Ret);

        // 動的にデリゲートを生成
        return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
    }
式木を使って動的にメソッドを生成した場合

[C#][.NET][式木] メタプログラミング入門 - 式木による Add メソッドの動的生成」でやったようにやってみよう。

    var item = new Something();

    Expression<Func<Something, string>> call = item => item.DoSomething();

のようなコードを実行して、Visual Studio のデバッガーの「クイックウォッチ」等で call の構造を調べてみる。

call 式の構造
call 式の構造

これを参考にして、式木によるメソッド生成を行ってみよう。

    // using namespace 省略

    // Expression (式) によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
    {
        // 生成したい式の例:
        // (T item) => item.methodName()

        // 引数 item の式
        var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
        // item.methodName() の式
        var callExpression      = Expression.Call(
                                      instance: parameterExpression,
                                      method  : typeof(T).GetMethod(methodName, Type.EmptyTypes)
                                  );
        // item => item.methodName() の式
        var lambda              = Expression.Lambda(callExpression, parameterExpression);
        // ラムダ式をコンパイルしてデリゲートとして返す
        return (Func<T, TResult>)lambda.Compile();
    }
Roslyn を使って動的にメソッドを生成した場合

[C#][.NET][Roslyn] メタプログラミング入門 - Roslyn による Add メソッドの動的生成」でやったようにやってみよう。

    // using namespace 省略

    // Roslyn によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
    {
        var engine  = new ScriptEngine(); // C# のスクリプトエンジン
        engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
        engine.ImportNamespace("System");        // System 名前空間のインポート
        var session = engine.CreateSession();

        // 作りたいソースコードの例
        // (Func<Something, string>)(item => item.DoSomething())

        // ソースコードを文字列として作成
        var code    = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
        // ソースコードをコンパイルしてデリゲートを生成して返す
        return (Func<T, TResult>)session.Execute(code: code);
    }
実行のテスト

まとめると次のようになる。

using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

// 検証用のクラス
public class Something
{
    // 検証用のメソッド
    public string DoSomething()
    {
        return "Hello!";
    }
}

static class Program
{
    // 普通の静的なメソッド呼び出し
    static string Call(Something item)
    {
        // 静的なメソッド呼び出し
        return item.DoSomething();
    }

    // リフレクションを使ったメソッド呼び出し
    static object CallByReflection(object item)
    {
        // リフレクションを使って動的にメソッド情報を取得
        var doSomething = item.GetType().GetMethod("DoSomething");
        // メソッドの動的呼び出し
        return doSomething.Invoke(item, null);
    }

    // dynamic を使ったメソッド呼び出し
    static dynamic CallByDynamic(dynamic item)
    {
        // dynamic による動的なメソッド呼び出し
        return item.DoSomething();
    }

    // Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
    {
        // DynamicMethod
        var method = new DynamicMethod(
            name          : "call"             ,
            returnType    : typeof(string)     ,
            parameterTypes: new[] { typeof(T) }
        );

        // 引数 item 生成用
        var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
        // ILGenerator
        var generator = method.GetILGenerator();

        // 生成したい IL
        // IL_0000: ldarg.0
        // IL_0001: callvirt instance void Something::DoSomething()
        // IL_0006: ret

        // 「引数をスタックにプッシュする」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_0);
        // 「指定された名前のメソッドを呼ぶ」コードを生成
        generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
        // 「リターンする」コードを生成
        generator.Emit(opcode: OpCodes.Ret);

        // 動的にデリゲートを生成
        return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
    }

    // Expression (式) によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
    {
        // 生成したい式の例:
        // (T item) => item.methodName()

        // 引数 item の式
        var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
        // item.methodName() の式
        var callExpression      = Expression.Call(
                                      instance: parameterExpression,
                                      method  : typeof(T).GetMethod(methodName, Type.EmptyTypes)
                                  );
        // item => item.methodName() の式
        var lambda              = Expression.Lambda(callExpression, parameterExpression);
        // ラムダ式をコンパイルしてデリゲートとして返す
        return (Func<T, TResult>)lambda.Compile();
    }

    // Roslyn によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
    {
        var engine  = new ScriptEngine(); // C# のスクリプトエンジン
        engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
        engine.ImportNamespace("System");        // System 名前空間のインポート
        var session = engine.CreateSession();

        // 作りたいソースコードの例
        // (Func<Something, string>)(item => item.DoSomething())

        // ソースコードを文字列として作成
        var code    = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
        // ソースコードをコンパイルしてデリゲートを生成して返す
        return (Func<T, TResult>)session.Execute(code: code);
    }

    static void Main()
    {
        実行のテスト();
    }

    static void 実行のテスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        // テスト用のアイテム
        var item               = new Something();

        // 普通の静的なメソッド呼び出し
        var answer             = Call            (item);
        Console.WriteLine("answer            : {0}", answer            );

        // リフレクションによる動的なメソッド呼び出し
        var answerByReflection = CallByReflection(item);
        Console.WriteLine("answerByReflection: {0}", answerByReflection);

        // dynamic による動的なメソッド呼び出し
        var answerByDynamic    = CallByDynamic   (item);
        Console.WriteLine("answerByDynamic   : {0}", answerByDynamic   );

        // Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
        var callByEmit         = CallByEmit      <Something, string>("DoSomething");
        var answerByEmit       = callByEmit      (item); // 生成したメソッドの呼び出し
        Console.WriteLine("answerByEmit      : {0}", answerByEmit      );

        // Expression (式) によるメソッド呼び出しメソッドの生成
        var callByExpression   = CallByExpression<Something, string>("DoSomething");
        var answerByExpression = callByExpression(item); // 生成したメソッドの呼び出し
        Console.WriteLine("answerByExpression: {0}", answerByExpression);

        // Roslyn によるメソッド呼び出しメソッドの生成
        var callByRoslyn       = CallByRoslyn    <Something, string>("DoSomething");
        var answerByRoslyn     = callByRoslyn    (item); // 生成したメソッドの呼び出し
        Console.WriteLine("answerByRoslyn    : {0}", answerByRoslyn    );
    }
}

実行してみると、次のようになる。

【実行のテスト】
answer            : Hello!
answerByReflection: Hello!
answerByDynamic   : Hello!
answerByEmit      : Hello!
answerByExpression: Hello!
answerByRoslyn    : Hello!

いずれの場合も、ちゃんとテスト用のアイテム item のメソッド DoSomething() を呼び出せていることが分かる。

実行に掛かった時間の測定用のクラス

実行に掛かった時間を測る為、今回も、次のクラスを用意する。

using System;
using System.Diagnostics;
using System.Linq.Expressions;

public static class パフォーマンステスター
{
    public static void テスト(Expression<Action> 処理式, int 回数, Action<string> output)
    {
        // 処理でなく処理式として受け取っているのは、文字列として出力する為
        var 処理 = 処理式.Compile();
        var 時間 = 計測(処理, 回数).TotalMilliseconds; // 回数分の処理に掛かったミリ秒数
        // 一回当たり何秒掛かったかを出力
        output(string.Format("{0,70}: {1,8:F}/{2} 秒", 処理式.Body.ToString(), 時間, 回数 * 1000));
    }

    static TimeSpan 計測(Action 処理, int 回数)
    {
        var stopwatch = new Stopwatch(); // 時間計測用
        stopwatch.Start();
        回数.回(処理);
        stopwatch.Stop();
        return stopwatch.Elapsed;
    }

    static void 回(this int @this, Action 処理)
    {
        for (var カウンター = 0; カウンター < @this; カウンター++)
            処理();
    }
}

それでは測っていこう。

デリゲートの動的生成のパフォーマンスのテスト

では、デリゲートの動的生成に掛かる時間を測ってみよう。

前回同様、デリゲートを動的生成する三種類のコードを呼び出し、それぞれがデリゲートを作成するまでに掛かる時間を測ってみる。

using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

// 検証用のクラス
public class Something
{
    // 検証用のメソッド
    public string DoSomething()
    {
        return "Hello!";
    }
}

static class Program
{
    // Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
    {
        // DynamicMethod
        var method = new DynamicMethod(
            name          : "call"             ,
            returnType    : typeof(string)     ,
            parameterTypes: new[] { typeof(T) }
        );

        // 引数 item 生成用
        var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
        // ILGenerator
        var generator = method.GetILGenerator();

        // 生成したい IL
        // IL_0000: ldarg.0
        // IL_0001: callvirt instance void Something::DoSomething()
        // IL_0006: ret

        // 「引数をスタックにプッシュする」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_0);
        // 「指定された名前のメソッドを呼ぶ」コードを生成
        generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
        // 「リターンする」コードを生成
        generator.Emit(opcode: OpCodes.Ret);

        // 動的にデリゲートを生成
        return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
    }

    // Expression (式) によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
    {
        // 引数 item の式
        var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
        // item.methodName() の式
        var callExpression      = Expression.Call(
                                      instance: parameterExpression,
                                      method  : typeof(T).GetMethod(methodName, Type.EmptyTypes)
                                  );
        // item => item.methodName() の式
        var lambda              = Expression.Lambda(callExpression, parameterExpression);
        // ラムダ式をコンパイルしてデリゲートとして返す
        return (Func<T, TResult>)lambda.Compile();
    }

    // Roslyn によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
    {
        var engine  = new ScriptEngine();
        engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
        engine.ImportNamespace("System");        // System 名前空間のインポート
        var session = engine.CreateSession();
        // ソースコードを文字列として作成
        var code    = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
        // ソースコードをコンパイルしてデリゲートを生成して返す
        return (Func<T, TResult>)session.Execute(code: code);
    }

    static void Main()
    {
        生成のパフォーマンステスト();
    }

    static void 生成のパフォーマンステスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        const int 回数 = 1000;

        // Reflectin.Emit による生成
        パフォーマンステスト(() => CallByEmit      <Something, string>("DoSomething"), 回数);
        // 式木による生成
        パフォーマンステスト(() => CallByExpression<Something, string>("DoSomething"), 回数);
        // Roslyn による生成
        パフォーマンステスト(() => CallByRoslyn    <Something, string>("DoSomething"), 回数);
    }

    static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
    {
        パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
    }
}

実行してみよう。

【生成のパフォーマンステスト】
                                                                         CallByEmit("DoSomething"):     5.66/1000000 秒
                                                                   CallByExpression("DoSomething"):   106.06/1000000 秒
                                                                       CallByRoslyn("DoSomething"):  3474.70/1000000 秒

速い順に並べてみよう。

順位 方法 時間 (マイクロ秒)
1 Reflection.Emit を使って動的にメソッドを生成した場合 5.66
2 式木を使って動的にメソッドを生成した場合 106.06
3 Roslyn を使って動的にメソッドを生成した場合 3474.70

前回同様、手間が掛からない方法程生成に時間が掛かっている。

生成済みデリゲートの実行のパフォーマンスのテスト

次は、それぞれの方法によるデリゲートを実行する時間を測ろう。

デリゲートを動的生成場合は、動的生成する迄の時間は測らず、生成後のデリゲートの実行時間を測る。

using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

// 検証用のクラス
public class Something
{
    // 検証用のメソッド
    public string DoSomething()
    {
        return "Hello!";
    }
}

static class Program
{
    // 普通の静的なメソッド呼び出し
    static string Call(Something item)
    {
        // 静的なメソッド呼び出し
        return item.DoSomething();
    }

    // リフレクションを使ったメソッド呼び出し
    static object CallByReflection(object item)
    {
        // リフレクションを使って動的にメソッド情報を取得
        var doSomething = item.GetType().GetMethod("DoSomething");
        // メソッドの動的呼び出し
        return doSomething.Invoke(item, null);
    }

    // dynamic を使ったメソッド呼び出し
    static dynamic CallByDynamic(dynamic item)
    {
        // dynamic による動的なメソッド呼び出し
        return item.DoSomething();
    }

    // Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
    {
        // DynamicMethod
        var method = new DynamicMethod(
            name          : "call"             ,
            returnType    : typeof(string)     ,
            parameterTypes: new[] { typeof(T) }
        );

        // 引数 item 生成用
        var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
        // ILGenerator
        var generator = method.GetILGenerator();

        // 生成したい IL
        // IL_0000: ldarg.0
        // IL_0001: callvirt instance void Something::DoSomething()
        // IL_0006: ret

        // 「引数をスタックにプッシュする」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_0);
        // 「指定された名前のメソッドを呼ぶ」コードを生成
        generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
        // 「リターンする」コードを生成
        generator.Emit(opcode: OpCodes.Ret);

        // 動的にデリゲートを生成
        return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
    }

    // Expression (式) によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
    {
        // 引数 item の式
        var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
        // item.methodName() の式
        var callExpression      = Expression.Call(
                                      instance: parameterExpression,
                                      method  : typeof(T).GetMethod(methodName, Type.EmptyTypes)
                                  );
        // item => item.methodName() の式
        var lambda              = Expression.Lambda(callExpression, parameterExpression);
        // ラムダ式をコンパイルしてデリゲートとして返す
        return (Func<T, TResult>)lambda.Compile();
    }

    // Roslyn によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
    {
        var engine  = new ScriptEngine();
        engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
        engine.ImportNamespace("System");        // System 名前空間のインポート
        var session = engine.CreateSession();
        // ソースコードを文字列として作成
        var code    = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
        // ソースコードをコンパイルしてデリゲートを生成して返す
        return (Func<T, TResult>)session.Execute(code: code);
    }

    static void Main()
    {
        実行のパフォーマンステスト();
    }

    static void 実行のパフォーマンステスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        // 検証用のアイテム
        var                      item             = new Something();

        // それぞれのデリゲートを準備
        Func<Something, string > call             = Call                                              ;
        Func<object   , object > callByReflection = CallByReflection                                  ;
        Func<dynamic  , dynamic> callByDynamic    = CallByDynamic                                     ;
        var                      callByEmit       = CallByEmit      <Something, string>("DoSomething");
        var                      callByExpression = CallByExpression<Something, string>("DoSomething");
        var                      callByRoslyn     = CallByRoslyn    <Something, string>("DoSomething");

        const int                回数             = 1000000;

        // デリゲートの動的生成を行わない場合
        パフォーマンステスト(() => Call            (item), 回数); // 静的メソッドを直接呼ぶ場合
        パフォーマンステスト(() => call            (item), 回数); // 静的メソッドをデリゲートに入れてから呼ぶ場合
        パフォーマンステスト(() => callByReflection(item), 回数); // リフレクションによる動的呼び出し
        パフォーマンステスト(() => callByDynamic   (item), 回数); // dynamic による動的呼び出し

        // 生成済みのデリゲートを使って呼ぶ場合
        パフォーマンステスト(() => callByEmit      (item), 回数); // Reflectin.Emit による生成
        パフォーマンステスト(() => callByExpression(item), 回数); // 式木による生成
        パフォーマンステスト(() => callByRoslyn    (item), 回数); // Roslyn による生成
    }

    static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
    {
        パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
    }
}

実行してみよう。

【実行のパフォーマンステスト】
                                                      Call(value(Program+<>c__DisplayClass2).item):    16.03/1000000000 秒
            Invoke(value(Program+<>c__DisplayClass2).call, value(Program+<>c__DisplayClass2).item):    10.74/1000000000 秒
Invoke(value(Program+<>c__DisplayClass2).callByReflection, value(Program+<>c__DisplayClass2).item):   316.62/1000000000 秒
   Invoke(value(Program+<>c__DisplayClass2).callByDynamic, value(Program+<>c__DisplayClass2).item):    57.93/1000000000 秒
      Invoke(value(Program+<>c__DisplayClass2).callByEmit, value(Program+<>c__DisplayClass2).item):    20.66/1000000000 秒
Invoke(value(Program+<>c__DisplayClass2).callByExpression, value(Program+<>c__DisplayClass2).item):    18.06/1000000000 秒
    Invoke(value(Program+<>c__DisplayClass2).callByRoslyn, value(Program+<>c__DisplayClass2).item):     8.01/1000000000 秒

速い順に並べてみよう。

順位 方法 時間 (ナノ秒)
1 Roslyn を使って動的にメソッドを生成した場合 8.01
2 静的メソッドをデリゲートに入れてから呼ぶ場合 10.74
3 静的メソッドを直接呼ぶ場合 16.03
4 式木を使って動的にメソッドを生成した場合 18.06
5 Reflection.Emit を使って動的にメソッドを生成した場合 20.66
6 dynamic を使った動的なメソッド呼び出し 57.93
7 リフレクションを使った動的なメソッド呼び出し 316.62

動的生成が終わってしまえば、その後のデリゲートの実行は通常のデリゲートと変わらない。

動的なメソッド呼び出しは呼び出す度に遅い (特にリフレクション) ので、場合によっては動的生成にパフォーマンス上のメリットがある。

キャッシュによる実行のパフォーマンステスト

つまり、動的にメソッドを生成する場合でも、生成済みのメソッドをうまくキャッシュしてやれば、パフォーマンスが大きく向上することになる。

キャッシュを使った場合と使わないで実行の度にメソッドを生成した場合の速度を測ってみよう。

先ずは、生成済みデリゲートをキャッシュして呼び出すクラスを作ろう。

デリゲートのキャッシュ クラス
using System;
using System.Collections.Generic;
using System.Text;

// デリゲートのキャッシュ
public static class DelegateCache
{
    // 生成したデリゲートのキャッシュ
    static readonly Dictionary<string, Delegate> methods = new Dictionary<string, Delegate>();

    // メソッド呼び出し
    public static TResult Call<T, TResult>(this T @this, string methodName, Func<string, Func<T, TResult>> generator)
    {
        var targetType = @this.GetType();
        var key        = ToKey(targetType, methodName);

        ToCache<T, TResult>(key: key, item: @this, methodName: methodName, generator: generator); // キャッシュする
        // キャッシュ内のデリゲートを呼ぶ
        return ((Func<T, TResult>)methods[key: key])(@this);
    }

    // キャッシュに入れる
    static void ToCache<T, TResult>(string key, T item, string methodName, Func<string, Func<T, TResult>> generator)
    {
        if (!methods.ContainsKey(key: key)) {     // キャッシュに無い場合は
            var method = generator(methodName);   // 動的にデリゲートを生成して
            methods.Add(key: key, value: method); // キャッシュに格納
        }
    }

    // キーに変換
    static string ToKey(Type type, string methodName)
    {
        return new StringBuilder().Append(type.FullName).Append(".").Append(methodName).ToString();
    }
}

それでは、実際に測ってみよう。

using Roslyn.Scripting.CSharp;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

// 検証用のクラス
public class Something
{
    // 検証用のメソッド
    public string DoSomething()
    {
        return "Hello!";
    }
}

static class Program
{
    // 普通の静的なメソッド呼び出し
    static string Call(Something item)
    {
        // 静的なメソッド呼び出し
        return item.DoSomething();
    }

    // リフレクションを使ったメソッド呼び出し
    static object CallByReflection(object item)
    {
        // リフレクションを使って動的にメソッド情報を取得
        var doSomething = item.GetType().GetMethod("DoSomething");
        // メソッドの動的呼び出し
        return doSomething.Invoke(item, null);
    }

    // dynamic を使ったメソッド呼び出し
    static dynamic CallByDynamic(dynamic item)
    {
        // dynamic による動的なメソッド呼び出し
        return item.DoSomething();
    }

    // Reflection.Emit の DynamicMethod によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
    {
        // DynamicMethod
        var method = new DynamicMethod(
            name          : "call"             ,
            returnType    : typeof(string)     ,
            parameterTypes: new[] { typeof(T) }
        );

        // 引数 item 生成用
        var item = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
        // ILGenerator
        var generator = method.GetILGenerator();

        // 生成したい IL
        // IL_0000: ldarg.0
        // IL_0001: callvirt instance void Something::DoSomething()
        // IL_0006: ret

        // 「引数をスタックにプッシュする」コードを生成
        generator.Emit(opcode: OpCodes.Ldarg_0);
        // 「指定された名前のメソッドを呼ぶ」コードを生成
        generator.Emit(opcode: OpCodes.Callvirt, meth: typeof(T).GetMethod(name: methodName, types: Type.EmptyTypes));
        // 「リターンする」コードを生成
        generator.Emit(opcode: OpCodes.Ret);

        // 動的にデリゲートを生成
        return (Func<T, TResult>)method.CreateDelegate(delegateType: typeof(Func<T, TResult>));
    }

    // Expression (式) によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
    {
        // 引数 item の式
        var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
        // item.methodName() の式
        var callExpression      = Expression.Call(
                                      instance: parameterExpression,
                                      method  : typeof(T).GetMethod(methodName, Type.EmptyTypes)
                                  );
        // item => item.methodName() の式
        var lambda              = Expression.Lambda(callExpression, parameterExpression);
        // ラムダ式をコンパイルしてデリゲートとして返す
        return (Func<T, TResult>)lambda.Compile();
    }

    // Roslyn によるメソッド呼び出しメソッドの生成
    static Func<T, TResult> CallByRoslyn<T, TResult>(string methodName)
    {
        var engine  = new ScriptEngine();
        engine.AddReference(typeof(T).Assembly); // 型 T のメソッドを参照するのに必要
        engine.ImportNamespace("System");        // System 名前空間のインポート
        var session = engine.CreateSession();
        // ソースコードを文字列として作成
        var code    = string.Format("(Func<{0}, {1}>)(item => item.{2}())", typeof(T).FullName, typeof(TResult).FullName, methodName);
        // ソースコードをコンパイルしてデリゲートを生成して返す
        return (Func<T, TResult>)session.Execute(code: code);
    }

    static void Main()
    {
        キャッシュによる実行のパフォーマンステスト();
    }

    static void キャッシュによる実行のパフォーマンステスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        // 検証用のアイテム
        var       item = new Something();

        const int 回数 = 1000;

        // コード生成しない場合
        パフォーマンステスト(() => Call            (item), 回数); // 静的メソッド
        パフォーマンステスト(() => CallByReflection(item), 回数); // リフレクションによる動的呼び出し
        パフォーマンステスト(() => CallByDynamic   (item), 回数); // dynamic による動的呼び出し

        // コード生成しつつ呼ぶ場合 (Reflection.Emit、式木、Roslyn の順)
        パフォーマンステスト(() => CallByEmit      <Something, string>("DoSomething")(item), 回数);
        パフォーマンステスト(() => CallByExpression<Something, string>("DoSomething")(item), 回数);
        パフォーマンステスト(() => CallByRoslyn    <Something, string>("DoSomething")(item), 回数);

        // コード生成にキャッシュを効かせた場合 (Reflection.Emit、式木、Roslyn の順)
        パフォーマンステスト(() => item.Call<Something, string>("DoSomething", methodName => CallByEmit      <Something, string>(methodName)), 回数);
        パフォーマンステスト(() => item.Call<Something, string>("DoSomething", methodName => CallByExpression<Something, string>(methodName)), 回数);
        パフォーマンステスト(() => item.Call<Something, string>("DoSomething", methodName => CallByRoslyn    <Something, string>(methodName)), 回数);
    }

    static void パフォーマンステスト(Expression<Action> 処理式, int 回数)
    {
        パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
    }
}

実行結果は次の通り。

【キャッシュによる実行のパフォーマンステスト】
                                                      Call(value(Program+<>c__DisplayClass6).item):     0.02/1000000 秒
                                          CallByReflection(value(Program+<>c__DisplayClass6).item):     0.41/1000000 秒
                                             CallByDynamic(value(Program+<>c__DisplayClass6).item):     0.05/1000000 秒
                         Invoke(CallByEmit("DoSomething"), value(Program+<>c__DisplayClass6).item):    52.71/1000000 秒
                   Invoke(CallByExpression("DoSomething"), value(Program+<>c__DisplayClass6).item):    97.02/1000000 秒
                       Invoke(CallByRoslyn("DoSomething"), value(Program+<>c__DisplayClass6).item):  2349.83/1000000 秒
  value(Program+<>c__DisplayClass6).item.Call("DoSomething", methodName => CallByEmit(methodName)):     6.88/1000000 秒
value(Program+<>c__DisplayClass6).item.Call("DoSomething", methodName => CallByExpression(methodName)):     3.36/1000000 秒
value(Program+<>c__DisplayClass6).item.Call("DoSomething", methodName => CallByRoslyn(methodName)):     3.49/1000000 秒

こちらも速い順に並べてみよう。

順位 方法 時間 (マイクロ秒)
1 静的メソッド 0.02
2 dynamic による動的呼び出し 0.05
3 リフレクションによる動的呼び出し 0.41
4 式木 (キャッシュ有り) 3.36
5 Roslyn (キャッシュ有り) 3.49
6 Reflection.Emit (キャッシュ有り) 6.88
7 Reflection.Emit (キャッシュ無し) 52.71
8 式木 (キャッシュ無し) 97.02
9 Roslyn (キャッシュ無し) 2349.83

キャッシュ テーブルのオーバーヘッドはあるが、動的生成を行う場合は、キャッシュがとても有効であることが分かる。

まとめ

今回は、とても簡単なメソッド呼び出しの例で、静的な例、動的な例、動的にコードを生成する三通りの例の比較を行った。また、動的にコードを生成する場合にはキャッシュが有効であることを示した。

次回からは、もう少し応用例をあげていきたい。

2013年11月07日

[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換を静的/動的に行う

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 - メソッド呼び出しのパフォーマンスの比較」の続き。

前回は、コード生成を行ってメソッド呼び出しを行う3通りの方法と静的なメソッド呼び出しや動的なメソッド呼び出しのパフォーマンスを比較した。

今回から、少しずつ応用に入っていきたい。

■ メタプログラミングの応用例

[C#][.NET] メタプログラミング入門 - はじめに」で、メタプログラミングが有効な例として次のようなものがあると述べた。

メタプログラミングが有効な例
  • コンパイラー/インタープリター
    ホスト言語のソースコードから動的に対象言語のプログラムを生成
  • O/R マッパー
    クラスやオブジェクトから動的に SQL を生成
  • XML や JSON の入出力
    クラスやオブジェクト等から動的に XMLJSON を生成/XML や JSON から動的にクラスやオブジェクト等を生成
    (生成するプログラムをプログラムで生成)
  • モック (mock) オブジェクト
    モック (ユニットテストで用いられる代用のオブジェクト) を動的に生成
    (生成するプログラムをプログラムで生成)
  • Web アプリケーション
    クライアント側で動作するプログラム (HTMLJavaScript 等) をサーバー側で動的に生成

この中から、今回は、「クラスやオブジェクト等から動的に文字列を生成する例」として、次にあげる例を見てみることにしよう。

クラスやオブジェクト等から動的に文字列を生成する例
  • デバッグ情報の出力
    クラスやオブジェクト等を、型情報を使って文字列に変換し、テストやデバッグで用いる。
  • ASP.NET (Web アプリケーション/Web サービス) のサーバー側の処理
    クラスやオブジェクト等を、型情報を使って HTML に変換し、出力する。
  • XML シリアライザー
    クラスやオブジェクト等を、型情報を使って XML に変換し、出力する。

これらについてメタプログラミング (動的コード生成) の例を示す前に、メタプログラミングによらない静的な方法とリフレクションによる動的な方法を見てみよう。

オブジェクトから文字列を生成する静的な例

先ずは、基本である静的な方法だ。

テスト用のクラス

文字列に変換する対象のクラスとして、次の単純な Book クラスを用意した。

// テスト用のクラス
public sealed class Book
{
    public string Title { get; set; }
    public int    Price { get; set; }
}

この Book クラスに対して、次の3通りを行うメソッドをそれぞれ作成しよう。

  • オブジェクトを、(デバッグ出力用の) 文字列に変換
  • オブジェクトを、XML に変換
  • オブジェクトを、HTML に変換
(デバッグ出力用の) 文字列に変換

これは、単純に文字列に変換するメソッドだ。Book クラスで ToString() メソッドをオーバーライドすることにする。

using System.Text;

// テスト用のクラス
public sealed class Book
{
    public string Title { get; set; }
    public int    Price { get; set; }

    // 文字列へ変換するメソッド ToString() をオーバーライド
    public override string ToString()
    {
        return new StringBuilder()                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
                   .Append("Title: ").Append(Title)
                   .Append(", "     )
                   .Append("Price: ").Append(Price)
                   .ToString();

        // または:
        //return string.Format("Title: {0}, Price: {1}", Title, Price);
    }
}
XML や HTML への変換

次は、XML や HTML に変換するメソッドだ。

XML への変換の拡張メソッドと HTML の table への変換の拡張メソッドを、それぞれ作成してみる。

using System.Collections.Generic;
using System.Text;

public static class BookExtensions
{
    // XML へ変換
    public static string ToXml(this IEnumerable<Book> @this)
    {
        var stringBuilder = new StringBuilder();
        stringBuilder.Append("<?xml version=\"1.0\"?>");
        @this.ForEach(
            book =>
               stringBuilder.Append("<Book>" )
                            .Append("<Title>").Append(book.Title).Append("</Title>")
                            .Append("<Price>").Append(book.Price).Append("</Price>")
                            .Append("</Book>")
        );
        return stringBuilder.ToString();
    }

    // HTML の table へ変換
    public static string ToHtmlTable(this IEnumerable<Book> @this)
    {
        var stringBuilder = new StringBuilder();
        stringBuilder.Append("<table><thead><tr><th>Title</th><th>Price</th></tr></thead><tbody>");
        @this.ForEach(
            book => stringBuilder.Append("<tr><td>").Append(book.Title).Append("</td>"     )
                                 .Append(    "<td>").Append(book.Price).Append("</td></tr>"));
        return stringBuilder.Append("</tbody></table>").ToString();
    }
}

上のコードで呼び出している ForEach メソッドは別クラスに拡張メソッドとして用意した。

using System;
using System.Collections.Generic;

// ForEach 用
public static class EnumerableExtensions
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (var item in @this)
            action(item);
    }
}
文字列を生成する静的な例のテスト

それでは、これらのメソッドを呼び出してみよう。

using System;
using System.Reflection;

static class Program
{
    static void Main()
    {
        文字列への変換のテスト();
        XMLへの変換のテスト   ();
        HTMLへの変換のテスト  ();
    }

    static void 文字列への変換のテスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        var book = new Book { Title = "Metaprogramming C#", Price = 3200 };

        Console.WriteLine(book.ToString());
    }

    static void XMLへの変換のテスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        var books = new[] {
            new Book { Title = "Metaprogramming C#", Price = 3200 },
            new Book { Title = "Metaprogramming VB", Price = 2100 },
            new Book { Title = "Metaprogramming F#", Price = 4300 }
        };

        Console.WriteLine(books.ToXml());
    }

    static void HTMLへの変換のテスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        var books = new[] {
            new Book { Title = "Metaprogramming C#", Price = 3200 },
            new Book { Title = "Metaprogramming VB", Price = 2100 },
            new Book { Title = "Metaprogramming F#", Price = 4300 }
        };

        Console.WriteLine(books.ToHtmlTable());
    }
}

実行結果は次のようになる。

【文字列への変換のテスト】
Title: Metaprogramming C#, Price: 3200
【XMLへの変換のテスト】
<?xml version="1.0"?><Book><Title>Metaprogramming C#</Title><Price>3200</Price></Book><Book><Title>Metaprogramming VB</Title><Pri
ce>2100</Price></Book><Book><Title>Metaprogramming F#</Title><Price>4300</Price></Book>
【HTMLへの変換のテスト】
<table><thead><tr><th>Title</th><th>Price</th></tr></thead><tbody><tr><td>Metaprogramming C#</td><td>3200</td></tr><tr><td>Metapr
ogramming VB</td><td>2100</td></tr><tr><td>Metaprogramming F#</td><td>4300</td></tr></tbody></table>

単純なデバッグ用の文字列と XML、HTML が出力されている。

オブジェクトから文字列を生成する動的な例 (リフレクションを使った例)

上記の静的な方法には、大きな問題点がある。これでは、対象とする型ごとに手でコードを書かなければならない。

上では Book クラスを用いて試しているが、Book クラス以外のクラスを対象とする場合には、また同様の3つのメソッドを作る必要があるだろう。

そこで、そのようなことにならないように、今度は、型情報から動的に処理を行うようにしてみよう。リフレクションを使ってみる。

リフレクションを使ってオブジェクトを動的に文字列や XML、HTML に変換

実際のコードは次のようになるだろう。静的な場合と比べてやや複雑になる。

using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

// リフレクションによる型によらない各種文字列への変換 (リフレクション版)
public static class ToStringByReflectionExtensions
{
    // 文字列へ変換 (リフレクション版)
    public static string ToStringByReflection<T>(this T @this)
    {
        // リフレクションで public なインスタンス プロパティの情報を全て取得
        var properties = @this.GetType().GetProperties(bindingAttr: BindingFlags.Instance | BindingFlags.Public);
        // LINQ でプロパティの情報の集まりから、読み込み可能なものに絞り込み、名前と値から作った文字列の集まりにする
        var textCollection = properties
                             .Where(property => property.CanRead)
                             .Select(property => string.Format("{0}: {1}", property.Name, property.GetValue(@this, null)));
        // string.Join で連結
        return string.Join(separator: ", ", values: textCollection);
    }

    // XML へ変換 (リフレクション版)
    public static string ToXmlByReflection<T>(this IEnumerable<T> @this)
    {
        var stringBuilder = new StringBuilder();
        stringBuilder.Append("<?xml version=\"1.0\"?>");

        var itemType      = typeof(T);

        // 要素の型のプロパティの一覧を取得
        var properties    = itemType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                    .Where(property => property.CanRead);

        @this.ForEach(
            // 要素毎の XML
            item => {
                stringBuilder.Append("<" ).Append(itemType.Name).Append(">");
                properties.ForEach(
                    // 要素のプロパティ毎の XML
                    property => stringBuilder.Append("<" ).Append(property.Name).Append(">")
                                             .Append(property.GetValue(item))
                                             .Append("</").Append(property.Name).Append(">")
                );
                stringBuilder.Append("</").Append(itemType.Name).Append(">");
            }
        );
        return stringBuilder.ToString();
    }

    // HTML の table へ変換 (リフレクション版)
    public static string ToHtmlTableByReflection<T>(this IEnumerable<T> @this)
    {
        var itemType      = typeof(T);

        // 要素の型のプロパティの一覧を取得
        var properties    = itemType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                    .Where(property => property.CanRead);
        if (properties.Count() == 0)
            return string.Empty;

        var stringBuilder = new StringBuilder();

        // table のヘッダー部の追加
        stringBuilder.Append("<table><thead><tr>");
        properties.ForEach(property => stringBuilder.Append("<th>").Append(property.Name).Append("</th>"));
        stringBuilder.Append("</tr></thead>");

        // table の本体を追加
        stringBuilder.Append("<tbody>");
        @this.ForEach(
            item => {
                stringBuilder.Append("<tr>");
                properties.Where(property => property.CanRead)
                          .ForEach(
                               property =>
                                   stringBuilder.Append("<td>").Append(property.GetValue(item)).Append("</td>")
                           );
                stringBuilder.Append("</tr>");
            }
        );
        return stringBuilder.Append("</tbody></table>").ToString();
    }
}

コードでは、ジェネリック プログラミングによって型への依存を無くしている。また、リフレクションで動的に型情報を利用することでも対象とするオブジェクトの型に依存しない処理を実現している。

こうすることにより、Book 以外のクラスのオブジェクトにも用いることができ、対象とするオブジェクトの型ごとにコードを書かなくて済む訳だ。

文字列を生成するリフレクションによる動的な例のテスト

こちらも呼び出してみよう。先の Program クラスを少し書き換えてリフレクション版のメソッドを呼ぶようにしてみる。

using System;
using System.Reflection;

static class Program
{
    static void Main()
    {
        文字列への変換のテスト();
        XMLへの変換のテスト   ();
        HTMLへの変換のテスト  ();
    }

    static void 文字列への変換のテスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        var book = new Book { Title = "Metaprogramming C#", Price = 3200 };

        Console.WriteLine(book.ToStringByReflection());
    }

    static void XMLへの変換のテスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        var books = new[] {
            new Book { Title = "Metaprogramming C#", Price = 3200 },
            new Book { Title = "Metaprogramming VB", Price = 2100 },
            new Book { Title = "Metaprogramming F#", Price = 4300 }
        };

        Console.WriteLine(books.ToXmlByReflection());
    }

    static void HTMLへの変換のテスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        var books = new[] {
            new Book { Title = "Metaprogramming C#", Price = 3200 },
            new Book { Title = "Metaprogramming VB", Price = 2100 },
            new Book { Title = "Metaprogramming F#", Price = 4300 }
        };

        Console.WriteLine(books.ToHtmlTableByReflection());
    }
}

実行してみると、結果は次のように静的な場合と変わらない。

【文字列への変換のテスト】
Title: Metaprogramming C#, Price: 3200
【XMLへの変換のテスト】
<?xml version="1.0"?><Book><Title>Metaprogramming C#</Title><Price>3200</Price></Book><Book><Title>Metaprogramming VB</Title><Pri
ce>2100</Price></Book><Book><Title>Metaprogramming F#</Title><Price>4300</Price></Book>
【HTMLへの変換のテスト】
<table><thead><tr><th>Title</th><th>Price</th></tr></thead><tbody><tr><td>Metaprogramming C#</td><td>3200</td></tr><tr><td>Metapr
ogramming VB</td><td>2100</td></tr><tr><td>Metaprogramming F#</td><td>4300</td></tr></tbody></table>

まとめ

ここまで、オブジェクトを文字列に変換する静的な方法と動的な方法を示した。動的な方法では、リフレクションを使うことにより柔軟な処理を行うことができた。

しかし、リフレクションには、前回試したように実行速度が遅い、という欠点がある。

次回からは、メタプログラミングを行い、動的にコードを生成する方法を試していきたい。

2013年11月08日

[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換を静的/動的に行う」の続き。

前回は、メタプログラミングによらない静的な方法とリフレクションによる動的な方法で、オブジェクトを文字列に変換した。

今回から、メタプログラミングによる動的コード生成を試していきたい。

■ 静的な方法とリフレクションによる動的な方法の問題点

メタプログラミングによる動的コード生成を試す前に、前回試した2つの方法の問題点を再確認しておこう。

静的な方法の問題点

前回は、Book クラスをデバッグ用文字列、XML、HTML に変換する静的なプログラミングの例をあげた。

対象とする型ごとに手でコードを書かなければならない、というのが問題点だった。

リフレクションによる動的な方法の問題点

前回は、ジェネリック プログラミングとリフレクションによって対象とするオブジェクトの型への依存を無くすことで、汎用的なプログラムを書くことができた。

だが、リフレクションは、動的に型情報を取得して利用する為に、実行速度が遅いという問題があった。

■ メタプログラミングによる動的コード生成による方法

そこで考えられるアプローチが、メタプログラミングだ。

つまり、静的なプログラミングの「対象とする型ごとに手でコードを書かなければならない」の「手で」の部分を、プログラムで生成してしまえば良いのではないか、ということだ。 「対象とする型ごとにプログラムでコードを生成し、それを用いる」のだ。

コード生成自体は、対象とするオブジェクトの型に依存せずに行う。ジェネリック プログラミングとリフレクションも用いることになるだろう。

しかし、一旦生成してしまえば、それを実行するのは静的なコードと変わらない。

[C#][.NET] メタプログラミング入門 - Add メソッドのパフォーマンスの比較」で調べたように、文字列変換の度にコード生成していたのでは、とても時間が掛かってしまう。「[C#][.NET] メタプログラミング入門 - メソッド呼び出しのパフォーマンスの比較」で行ったように、キャッシュを用いる必要があるだろう。生成したコードを対象の型毎にキャッシュにしまうことにしよう。

次回から、これまで行った3つの動的コード生成の方法で、オブジェクトの文字列への変換をやっていく。また、実行時のパフォーマンスに関しても調べることにしたい。

2013年11月09日

[C#][.NET] メタプログラミング入門 - 目次

Metasequoia

2013年11月10日

[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (Reflection.Emit 編)

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング」の続き。

Reflection.Emit によるメタプログラミング

それでは、Reflection.Emit を用いて文字列生成を行う例を見ていこう。

題材は 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換を静的/動的に行う」の中の「(デバッグ用の) 文字列に変換」だ。

今回も同様に、例えば、次のようなクラスのオブジェクトを文字列に変換する。

// テスト用のクラス
public sealed class Book
{
    public string Title { get; set; }
    public int    Price { get; set; }
}
Reflection.Emit によって生成したいプログラムの例

この Book クラスの場合、プログラムによって生成したいプログラムは、例えば、次のようなものだ。

    // 動的に作りたいコードの例:
    public static string ToString(Book book)
    {
        StringBuilder stringBuilder;
        stringBuilder       = new StringBuilder();
        stringBuilder.Append("Title: ");
        var           title = book.Title;
        stringBuilder.Append(title);
        stringBuilder.Append(", ");
        stringBuilder.Append("Price: ");
        var           price = book.Price;
        stringBuilder.Append(price);
        return stringBuilder.ToString();
    }
ILSpy を使って IL を見る

今回も、「[C#][.NET] メタプログラミング入門 - Reflection.Emit による Add メソッドの動的生成」でやったように、ILSpy を使ってこのコードの IL (Intermediate Language) を表示し、参考にしよう。

上記 ToString(Book book) メソッドを含むプログラムをビルドし (Release ビルド)、ILSpy で開くと次のように表示される。

ILSpy で Call メソッドの IL を見る
ILSpy で ToString メソッドの IL を見る

これを参考に IL を生成するコードを書いていこう。

これから書くプログラムは生成する方で、このプログラムで生成されるプログラムと混同しないように注意する必要がある。例えば、このプログラム生成プログラムは、Book クラスに依存しないように書く必要がある。動的で汎用的なものだ。これに対して、生成されるコードの方は、対象とするオブジェクトのクラス (例えば Book クラス) 専用の静的で高速なものだ。

表にしてみよう。

プログラム 手書き/生成 汎用性 プログラムの動作 動作速度
文字列変換プログラムを生成するプログラム 手書き 対象とするオブジェクトのクラス (例えば Book クラス) に依存せず汎用的 動的 遅い
文字列変換プログラム プログラムによって生成 対象とするオブジェクトのクラス (例えば Book クラス) 専用 静的 速い
Reflection.Emit によるオブジェクトの文字列への変換プログラム生成プログラム

では、IL を参考にプログラム生成プログラムを作ろう。

using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

// ToString メソッド生成器 (Reflection.Emit 版)
public static class ToStringGeneratorByEmit
{
    // メソッドを生成
    public static Func<T, string> Generate<T>()
    {
        // DynamicMethod
        var method = new DynamicMethod(
            name          : "ToString"         ,
            returnType    : typeof(string)     ,
            parameterTypes: new[] { typeof(T) }
        );

        // 引数 item 生成用
        var item      = method.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");
        // ILGenerator
        var generator = method.GetILGenerator();
        Generate(generator, typeof(T));

        // 動的にデリゲートを生成
        return (Func<T, string>)method.CreateDelegate(delegateType: typeof(Func<T, string>));
    }

    // メソッドを生成
    static void Generate(ILGenerator ilGenerator, Type targetType)
    {
        // 対象とする型の全プロパティ情報を取得
        var properties = targetType.GetProperties(bindingAttr: BindingFlags.Public | BindingFlags.Instance).Where(property => property.CanRead).ToArray();
        if (properties.Length > 0) {
            // 動的に作りたいコードの例 (実際のコードは targetType による):
            //public static string ToText(Book book)
            //{
            //    StringBuilder stringBuilder;
            //    stringBuilder = new StringBuilder();
            //    stringBuilder.Append("Title: ");
            //    var title     = book.Title;
            //    stringBuilder.Append(title);
            //    stringBuilder.Append(", ");
            //    stringBuilder.Append("Price: ");
            //    var price     = book.Price;
            //    stringBuilder.Append(price);
            //    return stringBuilder.ToString();
            //}

            // 動的に作りたい IL:
            // newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
            // stloc.0
            // ldloc.0

            var stringBuilderType = typeof(StringBuilder);
            // 「ローカルに StringBuilder を宣言する」コードを追加
            ilGenerator.DeclareLocal(localType: stringBuilderType);
            // 「StringBuilder のコンストラクターを使ってインスタンスを new する」コードを追加
            ilGenerator.Emit(opcode: OpCodes.Newobj, con: stringBuilderType.GetConstructor(Type.EmptyTypes));
            // 「現在の値 (StringBuilder のインスタンスへの参照) をスタックからポップし、ローカル変数に格納する」コードを追加
            ilGenerator.Emit(opcode: OpCodes.Stloc_0);
            // 「ローカル変数 (StringBuilder のインスタンスへの参照) をスタックに読み込む」コードを追加
            ilGenerator.Emit(opcode: OpCodes.Ldloc_0);

            // プロパティ毎に文字列に変換するコードを生成
            for (var index = 0; index < properties.Length; index++)
                GenerateForEachProperty(ilGenerator: ilGenerator, property: properties[index], needsSeparator: index < properties.Length - 1);

            // 「スタックからポップする」コードを追加
            ilGenerator.Emit(opcode: OpCodes.Pop);
            // 「ローカル変数 (StringBuilder のインスタンスへの参照) をスタックに読み込む」コードを追加
            ilGenerator.Emit(opcode: OpCodes.Ldloc_0);
            // 「StringBuilder のバーチャル メソッド ToString を呼ぶ」コードを追加
            ilGenerator.Emit(opcode: OpCodes.Callvirt, meth: stringBuilderType.GetMethod(name: "ToString", types: Type.EmptyTypes));
        } else {
            // 動的に作りたい IL:
            // ldstr ""

            // 「空の文字列をプッシュする」コードを追加
            ilGenerator.Emit(opcode: OpCodes.Ldstr, str: string.Empty);
        }
        // 動的に作りたい IL:
        // ret

        // 「リターンする」コードを追加
        ilGenerator.Emit(opcode: OpCodes.Ret);
    }

    // 文字列が引数の StringBuilder.Append メソッドが何度も使われるため static メンバーに
    static readonly MethodInfo appendMethod = typeof(StringBuilder).GetMethod(name: "Append", types: new[] { typeof(string) });

    // プロパティ毎に文字列に変換するコードを生成
    static void GenerateForEachProperty(ILGenerator ilGenerator, PropertyInfo property, bool needsSeparator)
    {
        // 動的に作りたいコードの例 (実際のコードは property による):
        // stringBuilder.Append("Title: ");
        // var title = item.Title;
        // stringBuilder.Append(title);

        // 動的に作りたい IL の例:
        // ldstr "Title: "
        // callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
        // ldarg.0
        // callvirt instance string Book::get_Title()
        // callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)

        // 「プロパティ名 + ": " をプッシュする」コードを追加
        ilGenerator.Emit(opcode: OpCodes.Ldstr, str: property.Name + ": ");
        // 「文字列が引数の StringBuilder.Append を呼ぶ」コードを追加
        ilGenerator.Emit(opcode: OpCodes.Callvirt, meth: appendMethod);

        // 「インスタンスへの参照をプッシュする」コードを追加
        ilGenerator.Emit(opcode: OpCodes.Ldarg_0);
        var propertyGetMethod = property.GetGetMethod(); // 渡されたプロパティの get メソッド
        // 「渡されたプロパティの get メソッドを呼ぶ」コードを追加
        ilGenerator.Emit(propertyGetMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, propertyGetMethod);

        var propertyGetMethodReturnType = propertyGetMethod.ReturnType; // 渡されたプロパティの get メソッドの戻り値の型
        //  渡されたプロパティの get メソッドの戻り値の型が引数の StringBuilder.Append メソッド
        var typedAppendMethod = typeof(StringBuilder).GetMethod(name: "Append", types: new[] { propertyGetMethodReturnType });

        // 型が違っていて、値型だった場合はボクシングするコードを追加
        if (typedAppendMethod.GetParameters()[0].ParameterType != propertyGetMethodReturnType &&
            propertyGetMethodReturnType.IsValueType)
            ilGenerator.Emit(opcode: OpCodes.Box, cls: propertyGetMethodReturnType);

        // 「渡されたプロパティの get メソッドの戻り値の型が引数の StringBuilder.Append メソッドを呼ぶ」コードを追加
        ilGenerator.Emit(opcode: OpCodes.Callvirt, meth: typedAppendMethod);

        if (needsSeparator) {
            // 動的に作りたいコード:
            // stringBuilder.Append(", ");

            // 動的に作りたい IL:
            // ldstr ", "
            // callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)

            // 「", " をプッシュする」コードを追加
            ilGenerator.Emit(opcode: OpCodes.Ldstr, str: ", ");
            // 「文字列が引数の StringBuilder.Append メソッドを呼ぶ」コードを追加
            ilGenerator.Emit(opcode: OpCodes.Callvirt, meth: appendMethod);
        }
    }
}
Reflection.Emit によるオブジェクトの文字列への変換 (キャッシュ無し)

それでは、これを使って変換メソッドを作ろう。先ずは、単純な「メソッドをキャッシュをせず、毎回コード生成するもの」から。

// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByEmitExtensions初期型
{
    // ToString に代わる拡張メソッド (Reflection.Emit 版)
    public static string ToStringByEmit初期型<T>(this T @this)
    {
        // 動的にメソッドを生成し、それを実行
        return ToStringGeneratorByEmit.Generate<T>()(@this);
    }
}
メソッドのキャッシュ無し版の動作テスト

次のような簡単なプログラムで動作させてみよう。

using System;

static class Program
{
    static void Main()
    {
        var book = new Book { Title = "Metaprogramming C#", Price = 3200 };

        Console.WriteLine(book.ToStringByEmit初期型());
    }
}

実行結果は次のようになる。

Title: Metaprogramming C#, Price: 3200

正しく動作する。

生成したメソッドのキャッシュ

上の "ToStringByEmit初期型" メソッドでは、呼び出される度にメソッドを生成している。 これでは、その度に時間が掛かる。 一度生成したメソッドは、キャッシュしておくことにしたい。

型毎に1つずつメソッドが必要なので、型毎に最初にメソッドが必要になったときにだけ生成し、それをキャッシュしておくことにしよう。 2回目からは、キャッシュにあるメソッドを使用するのだ。

この為、先にメソッド キャッシュ クラスを作成しよう。 次のようなものだ。Dictionary の中に型情報をキーとしてメソッドを格納する。 このクラスでは、ジェネリックを使い型に依存しないようにする。

using System;
using System.Collections.Generic;

//  生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
    // メソッド格納用
    readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();

    // メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
    public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
    {
        return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
    }

    // メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
    Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
    {
        var      targetType = typeof(T);
        Delegate method;
        if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
            method = generator();                                       // 動的にメソッドを生成して
            methods.Add(key: targetType, value: method);                // キャッシュに格納
        }
        return (Func<T, TResult>)method;
    }
}
Reflection.Emit によるオブジェクトの文字列への変換 (キャッシュ有り)

では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。

上のメソッド キャッシュ クラス MethodCache を利用して、次のようにする。

// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByEmitExtensions改
{
    // 生成したメソッドのキャッシュ
    static readonly MethodCache<string> toStringCache = new MethodCache<string>();

    // ToString に代わる拡張メソッド (Reflection.Emit 版)
    public static string ToStringByEmit改<T>(this T @this)
    {
        // キャッシュを利用してメソッドを呼ぶ
        return toStringCache.Call(item: @this, generator: ToStringGeneratorByEmit.Generate<T>);
    }
}
メソッドのキャッシュ有り版の動作テスト

こちらも動作させてみよう。

using System;

static class Program
{
    static void Main()
    {
        var book = new Book { Title = "Metaprogramming C#", Price = 3200 };

        Console.WriteLine(book.ToStringByEmit改());
    }
}

勿論、実行結果は同じだ。

Title: Metaprogramming C#, Price: 3200

まとめ

今回は、Reflection.Emit を用いて、動的に「オブジェクトを文字列に変換する」メソッドを生成するプログラムを作成した。

次回は、式木を使って同様のことを行う。

2013年11月11日

[C#][.NET] プログラミング C# (開発関連) - 目次

プログラミング C# (開発関連) - 目次

その他 C#関連

Windows ストア アプリ開発

2013年11月12日

[C#][.NET][式木] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (式木編)

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (Reflection.Emit 編)」の続き。

式木によるメタプログラミング

Reflection.Emit の次は、式木によって文字列生成を行う例を見ていこう。

題材は同じく 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換を静的/動的に行う」の中の「(デバッグ用の) 文字列に変換」だ。

今回もまた同様に、例えば、次のようなクラスのオブジェクトを文字列に変換する。

// テスト用のクラス
public sealed class Book
{
    public string Title { get; set; }
    public int    Price { get; set; }
}
式木によって生成したいプログラムの例

この Book クラスの場合、プログラムによって生成したい「文字列変換を行うラムダ式」は、例えば、次のようなものだ。

    // 動的に作りたいラムダ式の例 (実際のコードは targetType による):
    item => new StringBuilder().Append("Title: ").Append(item.Title)
                               .Append(", ")
                               .Append("Price: ").Append(item.Price)
                               .ToString()

このようなラムダ式を、これまで何度か行ったように、動的に組み立てることにしよう。

但し、上のラムダ式は、Book クラスの場合の例で、対象とするオブジェクトのクラスによって、必要なラムダ式は異なる。

そのため、ラムダ式を生成する部分は、リフレクションを用いて動的に行うことにする。 また、ジェネリックを用いて、型に依存しないようにする。

生成したラムダ式は、コンパイルしてデリゲートにする。 デリゲートになってしまえば、それは静的なコードと同様に動作させることができる。

そして、今回もデリゲートをキャッシュしておくことで、文字列変換を行う度に毎回デリゲートを動的に生成しなくても良いようにしよう。

式木によるオブジェクトの文字列への変換プログラム生成プログラム

では、上のようなラムダ式を生成し、コンパイルしてデリゲートとするプログラムを作ろう。

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

// ToString メソッド生成器 (式木版)
public static class ToStringGeneratorByExpression
{
    // メソッドを生成
    public static Func<T, string> Generate<T>()
    {
        // 対象とする型の全プロパティ情報を取得
        var            properties                  = typeof(T).GetProperties(bindingAttr: BindingFlags.Public | BindingFlags.Instance)
                                                              .Where(property => property.CanRead)
                                                              .ToArray();
        var parameterExpression = Expression.Parameter(typeof(T), "item");
        LambdaExpression lambdaExpression;
        if (properties.Length > 0) {
            // 動的に作りたいラムダ式の例 (実際のコードは targetType による):
            // item => new StringBuilder().Append("Title: ").Append(item.Title)
            //                            .Append(", ")
            //                            .Append("Price: ").Append(item.Price)
            //                            .ToString()

            var        callGetTypeOfItemExpression = Expression.Call(
                                                         instance: parameterExpression           ,
                                                         method  : typeof(T).GetMethod("GetType")
                                                     );
            Expression stringBuilderExpression     = Expression.New(type: typeof(StringBuilder));

            // プロパティ毎に文字列に変換する式を生成
            for (var index = 0; index < properties.Length; index++)
                stringBuilderExpression = GenerateForEachProperty(
                    property                   : properties[index]            , 
                    expression                 : stringBuilderExpression      ,
                    parameterExpression        : parameterExpression          ,
                    callGetTypeOfItemExpression: callGetTypeOfItemExpression  ,
                    needsSeparator             : index < properties.Length - 1
                );

            stringBuilderExpression                = stringBuilderExpression.CallMethod(typeof(StringBuilder).GetMethod("ToString", Type.EmptyTypes));
            lambdaExpression                       = Expression.Lambda(stringBuilderExpression, parameterExpression);
        } else {
            // 動的に作りたいラムダ式の例:
            // item => string.Empty
            lambdaExpression = Expression.Lambda(Expression.Constant(string.Empty, typeof(string)),
                                                 parameterExpression);
        }
        // ラムダ式をコンパイルしてデリゲートとして返す
        return (Func<T, string>)lambdaExpression.Compile();
    }

    // 何度も使われるメソッドは static メンバーに
    static readonly MethodInfo stringBuilderAppendStringMethod = typeof(StringBuilder).GetMethod("Append"     , new[] { typeof(string) });
    static readonly MethodInfo stringBuilderAppendObjectMethod = typeof(StringBuilder).GetMethod("Append"     , new[] { typeof(object) });
    static readonly MethodInfo typeGetPropertyMethod           = typeof(Type         ).GetMethod("GetProperty", new[] { typeof(string) });
    static readonly MethodInfo propertyInfoGetValueMethod      = typeof(PropertyInfo ).GetMethod("GetValue"   , new[] { typeof(object) });

    // プロパティ毎に文字列に変換する式を生成
    static Expression GenerateForEachProperty(PropertyInfo property, Expression expression, ParameterExpression parameterExpression, MethodCallExpression callGetTypeOfItemExpression, bool needsSeparator)
    {
        // 例えば、item.Title の式を生成 (実際のコードは property による)
        var callGetValueExpression = callGetTypeOfItemExpression
                                     .CallMethod(typeGetPropertyMethod         , Expression.Constant(property.Name)        )
                                     .CallMethod(propertyInfoGetValueMethod    , parameterExpression                       );

        // 例えば、stringBuilder.Append("Title: ").Append(item.Title) の式を生成 (実際のコードは property による)
        expression                 = expression
                                     .CallMethod(stringBuilderAppendStringMethod, Expression.Constant(property.Name + ": "))
                                     .CallMethod(stringBuilderAppendObjectMethod, callGetValueExpression                   );

        // 必要なら、stringBuilder.Append(", ") の式を生成
        if (needsSeparator)
            expression = expression.CallMethod(stringBuilderAppendStringMethod, Expression.Constant(", "));

        return expression;
    }

    // Expression.Call をメソッドチェーンにするための拡張メソッド
    static Expression CallMethod(this Expression @this, MethodInfo method, params Expression[] arguments)
    {
        return Expression.Call(@this, method, arguments);
    }
}
式木によるオブジェクトの文字列への変換 (キャッシュ無し)

それでは、これを使って変換メソッドを作ろう。先ずは、単純な「メソッドをキャッシュをせず、毎回コード生成するもの」から。

// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByExpressionExtensions初期型
{
    // ToString に代わる拡張メソッド (式木版)
    public static string ToStringByExpression初期型<T>(this T @this)
    {
        // 動的にメソッドを生成し、それを実行
        return ToStringGeneratorByExpression.Generate<T>()(@this);
    }
}
メソッドのキャッシュ無し版の動作テスト

今回も、次のような簡単なプログラムで動作させてみよう。

using System;

static class Program
{
    static void Main()
    {
        var book = new Book { Title = "Metaprogramming C#", Price = 3200 };

        Console.WriteLine(book.ToStringByExpression初期型());
    }
}

実行結果は次のようになる。

Title: Metaprogramming C#, Price: 3200

正しく動作する。

生成したメソッドのキャッシュ

前回同様、キャッシュを利用してみよう。

前回作成したメソッド キャッシュ クラスは次のようなものだった。

using System;
using System.Collections.Generic;

//  生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
    // メソッド格納用
    readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();

    // メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
    public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
    {
        return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
    }

    // メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
    Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
    {
        var      targetType = typeof(T);
        Delegate method;
        if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
            method = generator();                                       // 動的にメソッドを生成して
            methods.Add(key: targetType, value: method);                // キャッシュに格納
        }
        return (Func<T, TResult>)method;
    }
}
式木によるオブジェクトの文字列への変換 (キャッシュ有り)

では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。

上のメソッドキャッシュ クラス MethodCache を利用して、次のようにする。

// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByExpressionExtensions改
{
    // 生成したメソッドのキャッシュ
    static readonly MethodCache<string> toStringCache = new MethodCache<string>();

    // ToString に代わる拡張メソッド (式木版)
    public static string ToStringByExpression改<T>(this T @this)
    {
        // キャッシュを利用してメソッドを呼ぶ
        return toStringCache.Call(item: @this, generator: ToStringGeneratorByExpression.Generate<T>);
    }
}
メソッドのキャッシュ有り版の動作テスト

こちらも動作させてみよう。

using System;

static class Program
{
    static void Main()
    {
        var book = new Book { Title = "Metaprogramming C#", Price = 3200 };

        Console.WriteLine(book.ToStringByExpression改());
    }
}

実行結果は同じだ。

Title: Metaprogramming C#, Price: 3200

まとめ

今回は、前回の Reflection.Emit を用いた方法に続き、式木を使って動的に「オブジェクトを文字列に変換する」メソッドを生成するプログラムを作成した。

次回は、Roslyn による方法だ。

2013年11月13日

[C#][.NET][Roslyn] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (Roslyn 編)

Metasequoia

※ 「[C#][.NET][式木] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (式木編)」の続き。

Roslyn によるメタプログラミング

Roslyn によるメタプログラミングに関しては、以前、次にあげる記事で扱った。参考にしてほしい。

今回も、題材は同じく 「[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換を静的/動的に行う」の中の「(デバッグ用の) 文字列に変換」だ。

例えば、次のようなクラスのオブジェクトを文字列に変換する。

// テスト用のクラス
public sealed class Book
{
    public string Title { get; set; }
    public int    Price { get; set; }
}
Roslyn に渡す C# のソースコード

前回は、式木でラムダ式を組み立て、それをコンパイルすることにより、デリゲートを生成した。

Book クラスの場合を例にあげ、プログラムによって生成したい「文字列変換を行うラムダ式」として次のものを想定したのだった。

    // 動的に作りたいラムダ式の例 (実際のコードは targetType による):
    item => new StringBuilder().Append("Title: ").Append(item.Title)
                               .Append(", ")
                               .Append("Price: ").Append(item.Price)
                               .ToString()

対象とするオブジェクトのクラスによってラムダ式が異なるため、式木は動的に生成した。

今回は、「文字列変換を行うラムダ式」の C# のソースコードを動的に作成し、そのソースコードから Roslyn を用いてデリゲートを生成することにしよう。

先ず、Roslyn を使う前に、上のようなラムダ式の C# のソースコードを、リフレクションを用いて動的に作成する。

対象とするオブジェクトとその型から、C# のソースコードを文字列として作成するメソッドを書く。 この時、リフレクションとジェネリックを用いて、型に依存しないようにする。

// ToString メソッド生成器 (Roslyn 版)
public static class ToStringGeneratorByRoslyn
{
    // ToString() メソッドの C# のソースコードを作成する
    public static string CreateCodeOfToStringByRoslyn<T>()
    {
        // 動的に作りたい C# のソースコードの例 (実際のコードは typeof(T) による):
        // item => new StringBuilder().Append("Title: ").Append(item.Title)
        //                            .Append(", ")
        //                            .Append("Price: ").Append(item.Price)
        //                            .ToString()

        var bodyCode = "new StringBuilder()" +
                       string.Join(".Append(\", \")",
                                   typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                            .Where (property => property.CanRead)
                                            .Select(property => string.Format(".Append(\"{0}: \").Append(item.{0})", property.Name))) +
                       ".ToString()";
        return string.Format("(Func<{0}, string>)(item => {1})", typeof(T).FullName, bodyCode);
    }
}

これで正しい「文字列変換を行うラムダ式」の C# のソースコードが文字列として作成されるか、Book クラスの場合で試してみよう。

using System;

static class Program
{
    static void Main()
    {
        var code = ToStringGeneratorByRoslyn.CreateCodeOfToStringByRoslyn<Book>();
        Console.WriteLine(code);
    }
}

実行してみよう。

(Func<Book, string>)(item => new StringBuilder().Append("Title: ").Append(item.Title).Append(", ").Append("Price: ").Append(item.
Price).ToString())

目的とするソースコードができているようだ。ここまでは、まだ Roslyn は使用していない。

Roslyn によるオブジェクトの文字列への変換プログラム生成プログラム

この C# のソースコードから Roslyn を使ってデリゲートを生成しよう。このやり方は、「Roslyn による Add メソッドの動的生成」や「メソッド呼び出しのパフォーマンスの比較」で行ったのと同様だ。

次のようになる。

using Roslyn.Scripting.CSharp;
using System;
using System.Linq;
using System.Reflection;

// ToString メソッド生成器 (Roslyn 版)
public static class ToStringGeneratorByRoslyn
{
    // メソッドを生成
    public static Func<T, string> Generate<T>()
    {
        var code = CreateCodeOfToStringByRoslyn<T>(); // C# のソースコードを生成
        return Generate<T>(code: code);
    }

    // Roslyn でメソッドを生成
    static Func<T, string> Generate<T>(string code)
    {
        var engine  = CreateEngine(); // スクリプトエンジン
        var session = engine.CreateSession(); // 実行するには Session が必要
        return (Func<T, string>)session.Execute(code: code); // コードの生成
    }

    // Roslyn のスクリプトエンジンを作成する
    static ScriptEngine CreateEngine()
    {
        var engine = new ScriptEngine(); // Roslyn のスクリプトエンジン
        engine.ImportNamespace(@namespace: "System"     ); // System      名前空間を using
        engine.ImportNamespace(@namespace: "System.Text"); // System.Text 名前空間を using
        engine.AddReference(typeof(ToStringGeneratorByRoslyn).Assembly); // このアセンブリ内のクラスを使用する為に参照
        return engine;
    }

    // ToString() メソッドの C# のソースコードを作成する
    static string CreateCodeOfToStringByRoslyn<T>()
    {
        // 動的に作りたい C# のソースコードの例 (実際のコードは typeof(T) による):
        // item => new StringBuilder().Append("Title: ").Append(item.Title)
        //                            .Append(", ")
        //                            .Append("Price: ").Append(item.Price)
        //                            .ToString()

        var bodyCode = "new StringBuilder()" +
                       string.Join(".Append(\", \")",
                                   typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                            .Where(property => property.CanRead)
                                            .Select(property => string.Format(".Append(\"{0}: \").Append(item.{0})", property.Name))) +
                       ".ToString()";
        return string.Format("(Func<{0}, string>)(item => {1})", typeof(T).FullName, bodyCode);
    }
}
Roslyn によるオブジェクトの文字列への変換 (キャッシュ無し)

これを使って、これまでと同様、先ずはキャッシュ無しの変換メソッドを作ろう。 次のプログラムでは、呼ばれる度に毎回コードを生成する。

// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByRoslynExtensions初期型
{
    // ToString に代わる拡張メソッド (Roslyn 版)
    public static string ToStringByRoslyn初期型<T>(this T @this)
    {
        return ToStringGeneratorByRoslyn.Generate<T>()(@this);
    }
}
メソッドのキャッシュ無し版の動作テスト

次のような簡単なプログラムで動作させてみよう。

using System;

static class Program
{
    static void Main()
    {
        var book = new Book { Title = "Metaprogramming C#", Price = 3200 };

        Console.WriteLine(book.ToStringByRoslyn初期型());
    }
}

実行結果は次のようになり、正しく動作する。

Title: Metaprogramming C#, Price: 3200
生成したメソッドのキャッシュ

では、キャッシュを利用してみよう。

今回もメソッド キャッシュ クラスを使う。

using System;
using System.Collections.Generic;

//  生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
    // メソッド格納用
    readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();

    // メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
    public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
    {
        return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
    }

    // メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
    Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
    {
        var      targetType = typeof(T);
        Delegate method;
        if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
            method = generator();                                       // 動的にメソッドを生成して
            methods.Add(key: targetType, value: method);                // キャッシュに格納
        }
        return (Func<T, TResult>)method;
    }
}
Roslyn によるオブジェクトの文字列への変換 (キャッシュ有り)

では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。

上のメソッドキャッシュ クラス MethodCache を利用して、次のようにする。

// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByRoslynExtensions改
{
    // 生成したメソッドのキャッシュ
    static readonly MethodCache<string> toStringCache = new MethodCache<string>();

    // ToString に代わる拡張メソッド (Roslyn 版)
    public static string ToStringByRoslyn改<T>(this T @this)
    {
        // キャッシュを利用してメソッドを呼ぶ
        return toStringCache.Call(item: @this, generator: ToStringGeneratorByRoslyn.Generate<T>);
    }
}
メソッドのキャッシュ有り版の動作テスト

こちらも動作させてみよう。

using System;

static class Program
{
    static void Main()
    {
        var book = new Book { Title = "Metaprogramming C#", Price = 3200 };

        Console.WriteLine(book.ToStringByRoslyn改());
    }
}

やはり、実行結果は同じだ。

Title: Metaprogramming C#, Price: 3200

まとめ

今回は、前回の式木を用いた方法に続き、Roslyn を使って動的に「オブジェクトを文字列に変換する」メソッドを生成するプログラムを作成した。

次回は、メタプログラミングによる文字列変換のまとめとして、それぞれの方法でのパフォーマンスの比較を行う。

2013年11月14日

[C#][.NET] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (パフォーマンスのテスト)

Metasequoia

※ 「[C#][.NET][Roslyn] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (Roslyn 編)」の続き。

オブジェクトの文字列変換のメタプログラミング

ここまで、8通りの文字列変換メソッドを見てきた。

これらの方法について実行速度を比較していこう。

静的なメソッドに比べ、リフレクションによる動的なメソッドでは実行速度が低下する筈だ。また、メソッドの動的生成自体は遅いが、キャッシュがうまく効けば、リフレクションよりも速度面で有利な筈だ。

Add メソッドのパフォーマンスの比較」や「メソッド呼び出しのパフォーマンスの比較」で行ったように、実行速度を測ってみよう。

先ず、パフォーマンス測定用のクラスを用意する。

using System;
using System.Diagnostics;
using System.Linq.Expressions;

public static class パフォーマンステスター
{
    public static void テスト(Expression<Action> 処理式, int 回数, Action<string> output)
    {
        // 処理でなく処理式として受け取っているのは、文字列として出力する為
        var 処理 = 処理式.Compile();
        var 時間 = 計測(処理, 回数).TotalMilliseconds; // 回数分の処理に掛かったミリ秒数
        // 一回当たり何秒掛かったかを出力
        output(string.Format("{0,64}: {1,8:F}/{2} 秒", 処理式.Body.ToString(), 時間, 回数 * 1000));
    }

    static TimeSpan 計測(Action 処理, int 回数)
    {
        var stopwatch = new Stopwatch(); // 時間計測用
        stopwatch.Start();
        回数.回(処理);
        stopwatch.Stop();
        return stopwatch.Elapsed;
    }

    static void 回(this int @this, Action 処理)
    {
        for (var カウンター = 0; カウンター < @this; カウンター++)
            処理();
    }
}

テスト用のクラスは、これまでと同じ Book クラスだ。

// テスト用のクラス
public sealed class Book
{
    public string Title { get; set; }
    public int    Price { get; set; }
}
パフォーマンスの測定

では、計ってみよう。

次のプログラムを走らせてみる。

using System;
using System.Linq.Expressions;
using System.Reflection;

static class Program
{
    static void Main()
    {
        パフォーマンステスト();
    }

    static void パフォーマンステスト()
    {
        Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示

        var book = new Book { Title = "メタプログラミング C#", Price = 3800 };

        パフォーマンステスト(() => book.ToString                  ());
        パフォーマンステスト(() => book.ToStringByReflection      ());
        パフォーマンステスト(() => book.ToStringByEmit初期型      ());
        パフォーマンステスト(() => book.ToStringByEmit改          ());
        パフォーマンステスト(() => book.ToStringByExpression初期型());
        パフォーマンステスト(() => book.ToStringByExpression改    ());
        パフォーマンステスト(() => book.ToStringByRoslyn初期型    ());
        パフォーマンステスト(() => book.ToStringByRoslyn改        ());
    }

    static void パフォーマンステスト(Expression<Action> 処理式)
    {
        const int 回数 = 1000;
        パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine);
    }
}

次のようになった。勿論、動作環境などによって結果は異なる。

【パフォーマンステスト】
               value(Program+<>c__DisplayClass1).book.ToString():     1.11/1000000 秒
   value(Program+<>c__DisplayClass1).book.ToStringByReflection():    20.06/1000000 秒
      value(Program+<>c__DisplayClass1).book.ToStringByEmit初期型():   716.75/1000000 秒
        value(Program+<>c__DisplayClass1).book.ToStringByEmit改():     4.84/1000000 秒
value(Program+<>c__DisplayClass1).book.ToStringByExpression初期型():   466.80/1000000 秒
  value(Program+<>c__DisplayClass1).book.ToStringByExpression改():     2.65/1000000 秒
    value(Program+<>c__DisplayClass1).book.ToStringByRoslyn初期型():  4311.85/1000000 秒
      value(Program+<>c__DisplayClass1).book.ToStringByRoslyn改():     6.02/1000000 秒

速い順に並べてみよう。

順位 方法 時間 (マイクロ秒)
1 ToString() - ToString() をオーバーライドした静的なメソッド 1.11
2 ToStringByExpressionExtensions改() - キャッシュ有り 2.65
3 ToStringByEmit改() - キャッシュ有り 4.84
4 ToStringByRoslyn改() - キャッシュ有り 6.02
5 ToStringByReflection() - リフレクションによる動的なメソッド 20.06
6 ToStringByExpressionExtensions初期型() - キャッシュ無し 466.80
7 ToStringByEmit初期型() - キャッシュ無し 716.75
8 ToStringByRoslyn初期型() - キャッシュ無し 4311.85

ほぼ予想通りの結果となった。

静的なメソッドが最速だ。メソッドの動的生成はかなり遅い。キャッシュを行うことでこれらを大きく改善でき、場合によるが、リフレクションに対しても速度上のメリットがある。

まとめ

今回は、メタプログラミングによる文字列変換のまとめとして、パフォーマンスの比較を行った。

2013年11月15日

[Event] November 2013 MVP Global Summit - 出発前

Microsoft MVP Global Summit

MVP Global Summit 2013 (前回)

明日から、Microsoft MVP Global Summit に参加予定。今年は二回目。

世界中から、招待された数千人の Microsoft MVP が集まり、米国ワシントン州のマイクロソフト本社及びその周辺で、製品チームのマイクロソフトの社員と共に、沢山のマイクロソフトのテクノロジーに関するセッションが行われる。

セッションの内容も素晴らしいが、毎晩のように開催されるパーティなどでの、各国の MVP やマイクロソフトの技術者との交流の機会もとても貴重なものだ。

MVP Global Summit の記事

2013年11月23日

[Event] November 2013 MVP Global Summit

MVP Global Summit 2013 (前回)

※ 「November 2013 MVP Global Summit - 出発前」の続き。

Microsoft MVP Global Summit に参加してきた。9回めの参加。

C# の Microsoft MVP として、多くの技術セッションに参加することができた。

セッションを含めて英語漬けの毎日だったが、1人で他国の人達のテーブルに混ざって雑談する等、英語でのコミュニケーションにも少しは慣れてきた。

NDA (Non-disclosure agreement: 秘密保持契約) の為セッション内容等は公開できず、観光や食事、パーティの光景ばかりになるが、一部紹介したい。

11月16日 ― 0日目

時差対策もあり、Global Summit の一日前に着いて、隣国カナダのバンクーバーにドライブしてみることにした。 シアトル (Seattle) には何度も行っているが、カナダは初めて行った。

時差は17時間あり、この日は長い一日となる。

小松空港から ANABombardier Canadair Regional Jet 700成田空港へ。 関西国際空港の方が近いので、JR で行ってそちらから飛ぶことが多いのだが、今回はそちらに安い直行便が無かったので、成田から行くことにした。
成田空港で Global Wifi をレンタルし、ANA の Boeing 787 で米国ワシントン州最大の都市シアトルの南に位置するシアトル・タコマ空港 (Sea-Tac Airport)へ。
シアトル・タコマ空港に到着。
日本の MVP の方数人と合流してレンタカーを借りた。北上してカナダへ向かう。
シアトル ダウンタウンを横目に、州間高速道路5号線 (I-5) をどんどん北上。
シアトルの北のエバレット (Everett) というところで一休み。 シアトルで有名なシーフード レストランのアイバース (Ivar's) でランチ。 鮭のチャウダーとクラムのチップスを食べた。モールでコーチ (COACH) のバッグを土産に買ったり、スターバックスでシアトル コーヒーを楽しんだり、大型家電店のベスト・バイ (Best Buy) に寄ったり。
5号線を更に北上し、米国とカナダの国境を越えた。
カナダのブリティッシュコロンビア州バンクーバーに到着し、スーパーマーケットへ。4リットルの凄い色のアイス クリームが山積みに。
バンクーバーは中華が美味しいらしいので、中華レストランで夕食。 魚介類を中心に頼んだが、とても美味。
バンクーバーに入ってから適当なホテルを選んでチェックイン。

11月17日 ― 1日目

Global Summit の一日目。

朝ホテルを出発し、バンクーバーを少しドライブ。
ブリティッシュコロンビア大学 (The University of British Columbia)の中のUBC人類学博物館 (Museum of Anthropology at UBC) の前に、ロゼッタ・ストーンのような石が有った。 上は何語だろうか。
バンクーバーでよく見かけた「RIGHT TURNS YIELD TO PEDESTRIANS (右折では歩行者に道を譲れ: 右折時歩行者優先)」の看板。 C# の yield return っぽい。
カナディアン レストランでランチ。全てが巨大過ぎて食べ切れないが、どっしりとした肉の味に満足。
再び米国に入国し、シアトルのワシントン湖を挟んだ東の都市ベルビュー (Bellevue) へ。 Global Summit は、主にベルビューとマイクロソフト本社キャンパスがあるレドモンド (Redmond) で開催される。
途中何度か道を間違えて、予定より遅れたが、会場兼宿泊先のハイアット・リージェンシー・ベルビュー (Hyatt Regency Bellevue) に到着。 ホテルの部屋からの景色は既に夜景。 先ず Global Summit へのレジストレーションを行った。
先ずは、各国の有志の MVP がブースを構える MVP Showcase に参加。 (ちなみに、この MVP Showcase では日本の MVP の方が優勝
ホテル近くのベルビュー・スクエア (Bellevue Square) 内の Microsoft StoreSurface 2 を購入。
その後近くのイタリアン レストランで Microsoft MVP Dinner 2013 に参加。 日本を含むアジアの MVP の方々と交流した。
QFC (Quality Food Centers) に寄って、チーズやビールを購入。 ここのチーズやビールはとても美味しい。 チーズは、シアトル ダウンタウンのパイク・プレース・マーケット (Pike Place Market) で作られている Beecher's がお気に入り。 ビールは、シアトルで生まれた REDHOOKESB。 その後は、ホテルの部屋で交流した。

11月18日 ― 2日目

Global Summit の二日目。 レドモンドのマイクロソフト キャンパスでの技術セッションが始まった。

先ずは、ホテルで朝食。 ホテルの朝食は美味い。
マイクロソフト キャンパスへはバスで移動。
会場内には、コーヒーや茶、軽食などがいつも用意されていて、セッション中も含めて好きなだけ飲食できる。 冷蔵庫には缶飲料が冷えている。
セッション中。 全て英語。 内容は、撮影禁止でお伝えできない。 前日に買った Surface 2 を使用。
セッション会場でランチ。 ユニークな味。
セッションによっては、Tシャツなどのロゴ入りグッズが貰えることも。
セッションが終わって、バスでホテルに戻る。
再び、ベルビュー・スクエア (Bellevue Square) へ。 Microsoft Store で、米国では11月22日に発売の Xbox one を見に行った。
夜は、ホテルで Welcome Reception というパーティ。 世界中の MVP と交流した。 前の Global Summit で出会った方々と再会することもできた。 facebook で繋がっている人や、改めて facebook で繋がる人も多い。 いつもブラジル カラーで統一して元気なのは、ブラジルの MVP。 今回は、日本の MVP も青の T シャツで統一。
プールバーに移動して、Insiders Party by Infragistics という開発系 MVP 向けのパーティに参加。 各国の MVP と交流。 ここでは、こちらで良く飲むレドモンドの地ビール "Mac & Jack's" を頼んだ。

11月19日 ― 3日目

Global Summit の三日目。 この日もレドモンドのマイクロソフト キャンパスでの技術セッション。

ホテルで朝食後、バスでマイクロソフト キャンパスへ移動。
C# の父であるアンダース・ヘルスバーグ (Anders Hejlsberg) 氏にお会いしたので、買ったばかりの Surface 2 にサインしてもらったり、一緒に写真を撮ってもらったりした。
マイクロソフト キャンパスの様子とユニークな味のランチ。
この日の夜は、マイクロソフト キャンパス内の Commons という社員用の飲食店街で PG Evening Event というマイクロソフトの製品開発チームとのパーティがあった。
その後は、ホテルに戻り、近くの会場で GitHub Private Party に参加。 更に、他国の知人と近くのバーへ行ったり、GitHub パーティで知り合ったカナダの MVP を誘って部屋で飲むなどした。

11月20日 ― 4日目

Global Summit の四日目。 この日もレドモンドのマイクロソフト キャンパスでの技術セッション。

矢張りホテルで朝食の後、マイクロソフト キャンパスへバスで移動。
この日のランチもユニークな感じだったので、日本からいらしたマイクロソフトの社員の方に案内していただいて、マイクロソフトの社員の方用のレストランへ。 飲茶やスープなどを食べた。 美味しい。
最後の夜は、シアトル水族館 (Seattle Aquarium) を借り切った Attendee Party。 パーティの後は、いつものように近くのバーへ行ったり部屋で飲むなどした。

11月21日 ― 5日目

Global Summit は、もう一日続いたが、そちらには参加せず帰った。

ホテルの朝食が用意されていたが、空港で食べたいものがあったので取らずにホテル近くを散歩。 QFC で土産を買うなどした。
Shuttle Express を呼んで、シアトル・タコマ空港へ移動。 空港まで19ドル+チップ。 タクシーだと50ドル。バスだと乗るまでと降りてからスーツケースを引いて少し歩かないとならないが、2ドル程度と安い。
シアトル・タコマ空港に到着。 空港のアイバースのクラム・チャウダーとフィッシュ・チップを食べる。 最高に美味い。
往路と同じく、ANA の Boeing 787 で成田空港、更に ANA の Bombardier Canadair Regional Jet 700 で小松空港へ戻ってきた。

今回も、技術的にも多くの知識を得られた。

そして、マイクロソフトの方や多くの MVP の方と沢山の交流ができたのが何よりの財産だ。

参加の度に強く感じることだが、このような機会が得られたことは、エンジニアとしてとても幸せなことだと思う。

About 2013年11月

2013年11月にブログ「プログラミング C# - 翔ソフトウェア (Sho's)」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2013年10月です。

次のアーカイブは2013年12月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。

Powered by
Movable Type 3.35