はかせのラボ

私の頭の中を書いていく雑記ブログです

IL 同じミスはもうしたくないから拡張メソッドでお茶を濁す

あいさつ

どうも、はかせです。

前回ILでループ処理を作ろうとしたら
まさかのstaticメソッドとインスタンスメソッドを間違うという
ポカをやらかしてしまいました(´;ω;`)

前回
hakase0274.hatenablog.com

もう同じ過ちを繰り返したくないので、
今回は引数のEmitを拡張メソッドにしてしまいたいと思います。

やること

今回の実装はシンプルイズベストということで
メソッドがstaticか調べて、
staticの場合はldarg.0
そうでないならldarg.1をEmit
するという形にします。

ではさっそくコードです。

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

namespace ConsoleApp6
{
    static class ILUtility
    {
        /// <summary>
        /// 第一引数を取得
        /// </summary>
        /// <param name="il"></param>
        /// <param name="method"></param>
        public static void LdargFirst(this ILGenerator il,MethodBase method)
        {
            //staticメソッドならldarg.0
            if (method.IsStatic) il.Emit(OpCodes.Ldarg_0);
            //インスタンスメソッドならldarg.1
            else il.Emit(OpCodes.Ldarg_1);
        }

        /// <summary>
        /// 指定した引数を取得
        /// 使うかわからんけど思いついたから作ってみた
        /// </summary>
        /// <param name="il"></param>
        /// <param name="method"></param>
        /// <param name="index"></param>
        public static void Ldarg(this ILGenerator il, MethodBase method,int index)
        {
            //インスタンスメソッドなら第一引数はthisのため一個ずらす
            if (!method.IsStatic) index++;
            il.Emit(OpCodes.Ldarg, index);
        }
    }
}

メソッドがstaticかどうかはMethodBaseのIsStaticプロパティで取得できます。
MethodBaseってのはその名の通り
MethodInfoとかMethodBuilderとかの基底クラスです。

あとはそのプロパティでif文分岐して
それぞれEmitしていきます。

ではこれを実際に使ったIL生成のコードがこちら

//Hoge型からSumインスタンスメソッドを定義
//第一引数にメソッド名を、第二引数にはアクセス修飾子、第三引数は戻り値、第四引数はメソッドの引数
var sum = typeBuilder.DefineMethod("Sum", MethodAttributes.Public, typeof(int), new Type[] { typeof(int[]) });

// IL手書きはいつもどおり
var il = sum.GetILGenerator();

//ジャンプ先のラベルを宣言
var startLabel = il.DefineLabel();
var endLabel = il.DefineLabel();

//ローカル変数宣言
var iLoacl = il.DeclareLocal(typeof(int));
var iLoacl2 = il.DeclareLocal(typeof(int));

il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Br_S, startLabel);

il.MarkLabel(endLabel);
il.Emit(OpCodes.Ldloc_0);

//il.Emit(OpCodes.Ldarg_1)←これを拡張メソッドに置換
il.LdargFirst(sum);

il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ldelem_I4);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Stloc_1);

il.MarkLabel(startLabel);
il.Emit(OpCodes.Ldloc_1);

//il.Emit(OpCodes.Ldarg_1)←これを拡張メソッドに置換
il.LdargFirst(sum);

il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
il.Emit(OpCodes.Blt_S, endLabel);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret);

これでstaticメソッドでもインスタンスメソッドでも
引数を間違うというポカはやらかさない!・・・はず

あとがき

今回はIL手書きでミスったことを反省し、
二度と起こさないよう拡張メソッドを作った話でした。

今回はLdargだけやりましたけど、
CallとかBr_sとかも拡張メソッドでくるんでやるのが
安全なんでしょうね。

それでは今回はこの辺でノシ