LoginSignup
2
2

More than 1 year has passed since last update.

C#で動的にインターフェイスを実装するクラスを作成する(メソッド呼び出し)

Posted at

C#で動的にインターフェイスを実装するクラスを作成する(メソッド呼び出し)

はじめに

前回記事の続きです。

前回はGetter, Setterを持つシンプルなI/Fを実装する例でしたが、今回はメソッドを持つI/Fを実装します。そして、そのメソッドは別で実装されたメソッドを呼び出します(ベタ書きですが、疑似的なモック化を狙っています)。

以下、全コードのサンプルになります。

サンプル

IMyMockTypeを実現するMyMockTypeを作成、インスタンス化する
using System.Reflection;
using System.Reflection.Emit;

namespace DemoAssemblyBuilder
{
    /// <summary>
    /// Mock検証用インターフェイス
    /// </summary>
    public interface IMyMockType
    {
        /// <summary>
        /// 計算結果を返します
        /// </summary>
        /// <param name="number">入力値</param>
        /// <returns>計算結果</returns>
        int Calculate(int number);
    }

    public class Program
    {
        static void Main(string[] args)
        {
            // 以下のクラスを動的に作成します
            /*
            namespace MyExample
            {
                public class MyMockType : IMyMockType
                {
                    public int Calculate(int number)
                    {
                        return ...置き換えた処理を実行します...;
                    }
                }
            }
            */

            // アセンブリの定義
            AssemblyName assemblyName = new AssemblyName("MyExample");
            AssemblyBuilder assemblyBuilder =
                AssemblyBuilder.DefineDynamicAssembly(
                    assemblyName,
                    AssemblyBuilderAccess.Run);

            // モジュールの定義
            // モジュール名は多くの場合アセンブリ名と同じにすると良い
            ModuleBuilder moduleBuilder =
                assemblyBuilder.DefineDynamicModule(assemblyName.Name!);

            // 型情報の定義
            // 以降、MyMockTypeを作っていきます
            TypeBuilder typeBuilder = moduleBuilder.DefineType(
                "MyMockType",
                 TypeAttributes.Public);

            // IMyMockTypeを実現します
            Type myInterfaceType = typeof(IMyMockType);
            typeBuilder.AddInterfaceImplementation(myInterfaceType);

            // Calculateメソッドを定義します
            MethodBuilder meth = typeBuilder.DefineMethod(
                "Calculate",
                MethodAttributes.Public | MethodAttributes.Virtual,
                typeof(int),
                new Type[] { typeof(int) });

            // 引数を受取り、その引数を呼び出すメソッドの引数に渡します
            ILGenerator methIL = meth.GetILGenerator();
            methIL.Emit(OpCodes.Ldarg_1);
            var methodInfo = typeof(Program).GetMethod(nameof(MockFunc), BindingFlags.Static | BindingFlags.Public);
            methIL.Emit(OpCodes.Call, methodInfo!);
            methIL.Emit(OpCodes.Ret);

            typeBuilder.DefineMethodOverride(meth, typeof(IMyMockType).GetMethod("Calculate")!);

            // 以降、動作確認
            Type myMockType = typeBuilder.CreateType()!;
            IMyMockType mock = (IMyMockType)Activator.CreateInstance(myMockType)!;
            Console.WriteLine($"mock.Calculate : " + mock.Calculate(3));
            Console.ReadKey();
        }

        /// <summary>
        /// モック化したい処理の実装です
        /// </summary>
        /// <param name="number">入力値</param>
        /// <returns>計算結果</returns>
        public static int MockFunc(int number)
        {
            return number * number;
        }
    }
}

個人的な学習ポイント

  • OpCodes.Callの場合、MethodInfoを引数に渡して呼び出すことができる(他にもOpCodes.Callvirt, OpCodes.Newobjも大丈夫そうなのですが、他のOpCodesの意味は分かってないです)
  • 前回記事ではGetter, Setterで毎回methIL.Emit(OpCodes.Ldarg_0);と書いていたのでお約束的に必要なのかと思ったら、呼び出し元(this)が必要な場合のみ書くらしい

終わりに

普段は内部ロジックなんて書かないのですが、一回書いてみると新しい発見が見つかりますね。まだまだMoqのようにはいきませんが、MethodInfoを渡せることを知ったので、ラムダ関数の書き方で書いたActionやFuncをメソッドの引数に渡し、そこからMethodInfoを抽出すればMoqのようなことができそうな気がします。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2