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

プログラミング C# - 翔ソフトウェア (Sho's)

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 を用いて、動的にメソッドを生成するプログラムを作成した。

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