[C#] 51. Reflection機能を使い方 - Method


Study / C#    作成日付 : 2021/10/14 18:34:21   修正日付 : 2021/10/14 18:34:54

こんにちは。明月です。


この投稿はC#でReflection機能を使い方 - Methodに関する説明です。


以前の投稿でReflectionの機能を利用してクラスのインスタンスを生成する方法に関して説明しました。

link - [C#] 50. Reflection機能を使い方 - Class


Reflectionというのは簡単に説明すると、既存、ソースでnewキーワードを使ってインスタンスを生成する方法からStringの値により動的にインスタンスが生成されることということです。

関数(Method)も既存、ソース上で関数名を作成して呼び出す方法ではなく、クラス内で関数を探索して動的に実行するための方法ということです。

using System;

namespace Example
{
  // 例クラス
  class Node
  {
    // コンソールに出力する関数
    public void Print()
    {
      // コンソール出力
      Console.WriteLine("Hello world");
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // インスタンス生成
      var node = new Node();
      // 関数呼び出す。
      node.Print();
      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上の例は既存の方法でPrint関数を呼び出す方法です。

using System;

namespace Example
{
  // 例クラス
  class Node
  {
    // コンソールに出力する関数
    public void Print()
    {
      // コンソール出力
      Console.WriteLine("Hello world");
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // インスタンス生成
      Node node = new Node();
      // Nodeクラスのタイプを取得
      Type type = typeof(Node);
      // NodeクラスからPrint関数を取得
      var method = type.GetMethod("Print");
      // Print関数でインスタンスを入れて実行する。
      method.Invoke(node, null);
      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上の例はReflectionで関数を探して実行する方法です。ソース上ではStringのデータを利用してPrintの値で関数を呼び出します。

using System;
using System.Linq;

namespace Example
{
  // 例クラス
  class Node
  {
    // コンソールに出力する関数
    public void A()
    {
      // コンソール出力
      Console.WriteLine("A");
    }
    // コンソールに出力する関数
    public void B()
    {
      // コンソール出力
      Console.WriteLine("B");
    }
    // コンソールに出力する関数
    public void C()
    {
      // コンソール出力
      Console.WriteLine("C");
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // インスタンス生成
      Node node = new Node();
      // Nodeクラスのタイプを取得
      Type type = typeof(Node);
      // Nodeクラスの関数をすべて取得する。
      foreach(var method in type.GetMethods().Where(x => x.GetParameters().Length == 0 && x.ReturnType == typeof(void)))
      {
        // 関数を実行する。
        method.Invoke(node, null);
      }
      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上のGetMethods関数を利用してNodeクラスにあるすべての関数を取得しました。

でも、C#にはすべてのクラスはObjectクラスを継承します。なので、Objectの関数にも実行されます。それでパラメータが無くて、returnタイプがvoidということだけフィルターして実行しました。

結果はA,B,Cの関数が実行されました。


私は例のために上みたいに作成しましたが、Reflectionを利用するとユーザの入力値あるいはデータベースやファイルの値により実行できる関数を制御できます。簡単にインタープリターパターン(Interpret pattern)を作成することができます。

link - 作成中


そしてReflectionを利用すればpublicになっている関数だけではなく、private、protectedのアクセス修飾子にもアクセスができます。

using System;
using System.Reflection;

namespace Example
{
  // 例クラス
  class Node
  {
    // コンソール出力関数(privateアクセス修飾子に設定)
    private void Print()
    {
      // コンソール出力
      Console.WriteLine("Hello world");
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // インスタンス生成
      Node node = new Node();
      // Nodeクラスのタイプを取得
      Type type = typeof(Node);
      // Nodeクラスの関数をすべて取得する。 private、 protected、publicに関係せずにInstance関数を取得する。
      var method = type.GetMethod("Print", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
      // Print関数にインスタンスを入れて実行する。
      method.Invoke(node, null);
      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上の例でPrint関数はprivateで設定されていても、Main関数から実行することが確認できます。


我々が完璧なカプセル化でプロジェクトを作成しました。でもUnitテストのために途中で関数がしっかり作動しているかをテストする場合もあります。

祖の場合、Reflectionを利用してTestClassを作成してクラスのUnitテストをするとソースの修正なしでテストクラスを作成することも可能です。


そしてstaticとパラメータがある関数にもアクセスが可能です。

using System;
using System.Reflection;

namespace Example
{
  // 例クラス
  class Node
  {
    // コンソール出力関数(privateアクセス修飾子に設定)
    private static void Print(String str)
    {
      // コンソール出力
      Console.WriteLine(str);
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // Nodeクラスのタイプを取得
      Type type = typeof(Node);
      // Nodeクラスの関数をすべて取得する。 private、 protected、publicに関係せずにStatic関数を取得する。
      var method = type.GetMethod("Print", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
      // Print関数はstatic関数なのでインスタンス値が必要ない。パラメータは配列タイプですが、パラメータが一つだけなので一つの値だけ入れる。
      method.Invoke(null, new object[] { "Hello world" });
      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上の例はNodeクラスのPrintのstatic関数です。static関数はインスタンスに関係せずに呼び出す関数です。

なので、Invoke関数で関数を呼び出す時に、インスタンスの値を代わりにnullを入れます。パラメータの値はInvoke関数の二つ目のパラメータに配列タイプで入力します。


Reflectionの機能をよく利用すればプログラムをすごく動的に作成することがある利点があります。

Classと同じく、デザインパターンと関係がある機能だし、特にNUnitのUnitテストでよく使う機能です。


でも、Reflection機能はVisual studioのデバック環境でエラーをキャッチしてくれないので、バグの危険性と可読性が悪くなる欠点があります。

そして一般静的な呼び出しより探索してインスタンスを入れて実行する構造なので性能にも影響がある機能です。なので仕様に合わせて使うことをお勧めです。


ここまでC#でReflection機能を使い方 - Methodに関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

#C#
最新投稿