ラムダ式を利用してクラスのメソッドを動的に呼び出す

ラムダ式を利用してクラスのメソッドを動的に呼び出すコードを紹介します。

プログラム1 : dynamic を利用したシンプルな呼び出し

はじめにラムダ式など利用せず、dynamic型を利用した呼び出しコードを紹介します。
Windows Formアプリケーションを作成します。

UI

下図のフォームを作成します。
ラムダ式を利用してクラスのメソッドを動的に呼び出す:画像1

コード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq.Expressions;

namespace DynamicDelegate
{
  public class MyClass
  {
    public string MyMethod()
    {
      return "Hello!";
    }
  }

  public partial class FormSimpleDynamicDelegate : Form
  {
    public FormSimpleDynamicDelegate()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      var myc = new MyClass();
      dynamic dyn = myc;
      textBox1.Text = dyn.MyMethod();
    }

  }
}

解説

ボタンのクリックにより下記のコードが実行されます。

MyClassのインスタンスを作成します。var で変数宣言していますので型推論により、MyClass型の変数となります。

  var myc = new MyClass();

作成したインスタンスを格納しているMyClass型の値を dynamic型の変数に代入します。

  dynamic dyn = myc;


dynamic型の変数に代入したMyClassインスタンスオブジェクトのMyMethod()メソッドを呼び出し、結果をテキストボックスに表示します。MyMethod() メソッドは "Hello!"の文字列を返します。

  textBox1.Text = dyn.MyMethod();

補足
var 変数のオブジェクトは下記のコードはコンパイルエラーになります。クラスに存在しないメソッドのチェックはコンパイル時に行われます。

  textBox1.Text = myc.MyMethodDummy();

一方、dynamic型の場合は、型チェックが実行時のため、クラスに存在しないメソッドを記述してもコンパイルエラーは発生しません。

  textBox1.Text = dyn.MyMethodDummy();

実行結果

上記のプロジェクトを実行します。下図のフォームが表示されます。
ラムダ式を利用してクラスのメソッドを動的に呼び出す:画像2

[button1]をクリックします。テキストボックスに "Hello!" の文字列が表示されます。~ ラムダ式を利用してクラスのメソッドを動的に呼び出す:画像3

プログラム2 : CreateDelegateを利用したシンプルな呼び出し

Windows Formアプリケーションを作成します。

UI

下図のフォームを作成します。先のプログラムのフォームにボタンを一つ追加したものになります。

ラムダ式を利用してクラスのメソッドを動的に呼び出す:画像4

コード

下記のコードを記述します。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq.Expressions;

namespace DynamicDelegate
{
  public class MyClass
  {
    public string MyMethod()
    {
      return "Hello!";
    }
  }

  public partial class FormSimpleDynamicDelegate : Form
  {
    public FormSimpleDynamicDelegate()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
    }

    private void button2_Click(object sender, EventArgs e)
    {
      var myc = new MyClass();
      var callByEmit = CallByEmit<MyClass, string>("MyMethod");
      var answerByEmit = callByEmit(myc);
      textBox1.Text = answerByEmit.ToString();
    }

    static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
    {
      DynamicMethod dmethod = new DynamicMethod(
        "call",
        typeof(string),
        new[] { typeof(T) });

      var item = dmethod.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");

      var generator = dmethod.GetILGenerator();
      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>)dmethod.CreateDelegate(delegateType: typeof(Func<T, TResult>));
    }
  }
}

解説

[button2]をクリックすると以下の処理を実行します。

MyClass クラスのインスタンスを作成します。

  var myc = new MyClass();

CallByEmitメソッドを呼び出し、メソッドのデリゲートを取得します。取得するメソッド名を引数に与えます。

  var callByEmit = CallByEmit<MyClass, string>("MyMethod");

CallByEmit メソッドで取得したデリゲートを呼び出します。デリゲートを呼び出すことで、MyClassオブジェクトのMyMethodメソッドを呼び出す処理になります。 メソッドの戻り値はデリゲートの戻り値になります。

  var answerByEmit = callByEmit(myc);


メソッドの戻り値をテキストボックスに表示します。

  textBox1.Text = answerByEmit.ToString();

デリゲートの取得メソッドは下記コードです。DynamicMethod オブジェクトを利用してクラスのデリゲートを取得します。

    static Func<T, TResult> CallByEmit<T, TResult>(string methodName)
    {
      DynamicMethod dmethod = new DynamicMethod(
        "call",
        typeof(string),
        new[] { typeof(T) });

      var item = dmethod.DefineParameter(position: 1, attributes: ParameterAttributes.In, parameterName: "item");

      var generator = dmethod.GetILGenerator();
      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>)dmethod.CreateDelegate(delegateType: typeof(Func<T, TResult>));
    }

実行結果

上記のプロジェクトを実行します。下図のフォームが表示されます。
ラムダ式を利用してクラスのメソッドを動的に呼び出す:画像5

[button2]をクリックします。テキストボックスに "Hello!" の文字列が表示されます。~ ラムダ式を利用してクラスのメソッドを動的に呼び出す:画像6

プログラム3 : ラムダ式を利用したシンプルな呼び出し

先ほどのプログラムでは、デリゲートを動的に生成するため、CreateDelegate メソッドを呼び出しましたが、 このプログラムではラムダ式を利用した、シンプルな記述コードを紹介します。

UI

下図のUIを作成します。

コード

下記のコードを記述します。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq.Expressions;

namespace DynamicDelegate
{
  public class MyClass
  {
    public string MyMethod()
    {
      return "Hello!";
    }
  }

  public partial class FormSimpleDynamicDelegate : Form
  {
    public FormSimpleDynamicDelegate()
    {
      InitializeComponent();
    }

    private void button3_Click(object sender, EventArgs e)
    {
      var myc = new MyClass();
      var callByExpression = CallByExpression<MyClass, string>("MyMethod");
      var answerByExpression = callByExpression(myc);
      textBox1.Text = answerByExpression.ToString();

    }

    static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
    {
      // (T item) => item.methodName()

      var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
      var callExpression = Expression.Call(
                                    instance: parameterExpression,
                                    method: typeof(T).GetMethod(methodName, Type.EmptyTypes)
                                );
      var lambda = Expression.Lambda(callExpression, parameterExpression);
      return (Func<T, TResult>)lambda.Compile();
    }


  }
}

解説

[button3]がクリックされた際に実行される下記のコードは、先のCreateDelegate を利用する場合のコードとほぼ同様です。
MyClassインスタンスを生成し、CallByExpression メソッドで呼び出しメソッドのデリゲートを取得します。CallByExpression メソッドの引数に 取得したいクラスメソッドの名前を与えます。
取得したデリゲートを呼び出すことで、MyClassの MyMethod メソッドを呼び出す動作になります。

  var myc = new MyClass();
  var callByExpression = CallByExpression<MyClass, string>("MyMethod");
  var answerByExpression = callByExpression(myc);
  textBox1.Text = answerByExpression.ToString();


デリゲートを返すCallByExpressionメソッドのコードが下記です。CallByExpressionメソッドでは、CreateDelegate は利用せず、 ラムダ式を返すことでデリゲートを返します。
生成されるラムダ式は次のものになります。

静背されるラムダ式
(T item) => item.methodName()


Expression.Parameter メソッドを利用してラムダ式のパラメータを作成します。また、Expression.Call メソッドを利用してラムダ式の呼び出しで メソッドの呼び出しをするラムダ式を作成します。
ラムダ式の作成は、Expression.Lambda メソッドで作成します。メソッドの引数に、呼び出しの定義callExpression とパラメーターの定義 parameterExpression を与えます。ラムダ式のオブジェクトはLambda メソッドの戻り値になります。
ラムダ式のオブジェクトの Complie メソッドを呼び出しデリゲートを作成します。CallByExpression メソッドの戻り値として作成したデリゲートを返します。

 
  static Func<T, TResult> CallByExpression<T, TResult>(string methodName)
  {
    var parameterExpression = Expression.Parameter(type: typeof(T), name: "item");
    var callExpression = Expression.Call(
                                  instance: parameterExpression,
                                  method: typeof(T).GetMethod(methodName, Type.EmptyTypes)
                              );
    var lambda = Expression.Lambda(callExpression, parameterExpression);
    return (Func<T, TResult>)lambda.Compile();
  }

実行結果

補足 : CallByExpression はstaticの必要があるか

AuthorPortraitAlt
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
作成日: 2020-04-26
Copyright © 1995–2025 iPentec all rights reserverd.