さて皆さん、
先日ワテはC#でプログラムを書いていて、こういう状況に遭遇した。
以下のようなごく普通のC#Consoleプロジェクトを実行してそのプログラムの中で、
- プロジェクト内に存在するクラス名を文字列として取得したり、
- そのクラス内のメソッドやプロパティを文字列として取得したり、
- 文字列として取得したメソッドを実行したり
したい状況に遭遇した。
どういう状況でそんな事をしたくなったのかはややこしいので説明は省略する。
以下は、ワテの備忘録としてそれらの実験結果を記事にまとめた。
MSDNにクラスやメソッドを文字列で操作するサンプルがあった
参考となるプログラムをMSDNのサイトで見つけた。
以下のコードはこのサンプルと殆ど同じ。違いはNameSpaceの文字列を追加した程度。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; namespace MyConsoleApp { public class MagicClass { private int magicBaseValue; public MagicClass() { magicBaseValue = 9; } public int ItsMagic(int preMagic) { return preMagic * magicBaseValue; } } public class TestMethodInfo { public static void Main() { // Get the constructor and create an instance of MagicClass // コンストラクタを取得してMagicClassのインスタンスを作成する //org Type magicType = Type.GetType("MagicClass"); // <=== MSDNサンプルオリジナル Type magicType = Type.GetType("MyConsoleApp.MagicClass");// <=== NameSpaceを追加 ConstructorInfo magicConstructor = magicType.GetConstructor(Type.EmptyTypes); object magicClassObject = magicConstructor.Invoke(new object[] { }); // Get the ItsMagic method and invoke with a parameter value of 100 // ItsMagicメソッドを取得して、引数100で実行しその結果をmagicValue変数に取得 MethodInfo magicMethod = magicType.GetMethod("ItsMagic"); object magicValue = magicMethod.Invoke(magicClassObject, new object[] { 100 }); Console.WriteLine("MethodInfo.Invoke() Example\n"); Console.WriteLine("MagicClass.ItsMagic() returned: {0}", magicValue); } } }
このサンプルを実行すると以下のように出力される:
MethodInfo.Invoke() Example MagicClass.ItsMagic() returned: 900 続行するには何かキーを押してください . . .
処理の流れとしては、文字列で与えられたクラス名 “MagicClass” に対して、
- GetType()
- GetConstructor()
- Invoke()
などの関数を使って、クラスMagicClassのインスタンスを作成する。
次に、そのインスタンスのItsMagicメソッドを取得して実行している。
- GetMethod()
- Invoke()
と言う感じ。
その結果、ItsMagicメソッドの実行結果として 900 と言う値が画面に表示される。
なかなか便利な機能だ。
これらの処理を行うためには、以下の参照設定が必要となる。
using System.Reflection;
ワテ自身、このリフレクションと言う奴はたまに使うのだが、その仕組みは良く理解できていない。でもこのリフレクションと言うのを使うと上記の例以外にも、いろいろと面白い事が可能になる。
C#, C, C++で現在実行している関数の名前を取得したい
プログラミングにおいて、現在実行している関数の名前を取得したい場合は良くある。
関数名が必要になる最も多い状況は、恐らくデバッグ作業中だろう。
原因不明のエラーが出た。
デバッガーでソースコードをステップ実行出来ればどの関数のどの場所でエラーしているのかは分る。
でも、例えばサーバーで動いているプログラムに対してリモートデバッグが出来ない状況などもある。
そいう言う状況では、プログラムの中に出力コマンドを多数入れておいて、今どの関数に入ったのかをログファイルに出力するなどの泥臭いデバッグ作業は良く行われる。
C/C++の場合
例えばCやC++の場合なら、定義済みマクロ
__LINE__ __func__
などがある。
これを使うと、C/C++で実行中の行と実行中の関数名を取得出来る。
printf("今実行中の関数名は :%s\n", __func__); printf("今実行中の行番号は :%s\n", __LINE__);
などか。
ちなみにVisual StudioのC/C++場合なら、
__FUNCTION__ // char版 __FUNCTIONW__ // wchar_t版
などがある。
まあ、C/C++の場合にはコンパイラーでコンパイルする時点で、これらのマクロが評価されて実行プログラムに関数名が文字列定数として埋め込まれるのだ。
なので分かり易い。
C#の場合
一方、C#の場合には、コンパイルした時点では MSIL(Microsoft Intermediate Language)と言う中間言語だ。
それを実行した場合には、MSILがネイティブ コードに変換される訳だが、そう言う二段階の変換が行われるので、実行時に関数名の取得をするには上述のC/C++のようには簡単には行かないようだ。
まあ .NET Frameworkと言うのが介在するので、色々とややこしいみたい。
しかしまあ、以下に示すようなヘンテコな手法を使えば、C#でも実行時に関数名を取得する事は出来なくはない。
using System; using mb = System.Reflection.MethodBase; using mi = System.Reflection.MethodInfo; namespace ReflectionTestConsoleApp { class Program { static void Main(string[] args) { function_info(mb.GetCurrentMethod()); function_info(mi.GetCurrentMethod()); } public static void function_info(System.Reflection.MethodBase get_current_method) { string name_space = get_current_method.DeclaringType.Namespace; string class_name = get_current_method.DeclaringType.Name; string method_name = get_current_method.ToString(); string func_name = get_func_name(method_name); Console.WriteLine("現在実行中の名前空間 :->{0}<-", name_space); Console.WriteLine("現在実行中のクラス名 :->{0}<-", class_name); Console.WriteLine("現在実行中のメソッド名:->{0}<-", method_name); Console.WriteLine("現在実行中の関数名のみ:->{0}<-", func_name); } private static string get_func_name(string func_full_name) { string func_name = ""; // 関数名のみ取り出すのだが int i = func_full_name.IndexOf(" "); // 即席で作ったのでヘンテコだ。 if (i >= 0) // まあ、そこは指摘しないで { // 頂きたい。 func_name = func_full_name.Substring(i + 1); // i = func_name.IndexOf("("); // func_name = func_name.Substring(0, i + 1) + ")";// } return func_name; } } }
その実行結果は以下の通り。
現在実行中の名前空間 :->ReflectionTestConsoleApp<- 現在実行中のクラス名 :->Program<- 現在実行中のメソッド名:->Void Main(System.String[])<- 現在実行中の関数名のみ:->Main()<- 続行するには何かキーを押してください . . .
ちなみに、以下のどちらを使っても同じ結果になる。
using mb = System.Reflection.MethodBase; using mi = System.Reflection.MethodInfo;
なので、どちらか一方を使えば良いのだが
- 何故同じ結果が得られるのか、
- そもそも両者は何が違うのか、
- それ以前にこのReflectionという機能は何なの?
と言う点に関しては、ワテは理解できていない。
と言う事で、System.Reflection と言うのは良く分からんが何かと面白い小細工関数を作成出来るので気に入っている。
なぜMethodBaseとMethodInfoと言う似たような二つの仕組みがあるのか?
(追記)
この件に関してずっと気になっていたのだが、本日ようやく解決した。
ChatGPT3.5さんに質問した結果、詳しく教えて貰えたので以下にそのチャット内容を紹介しておく。
リフレクションを使う場合、どちらかのusingを使えば良いのは知っているのだが、なぜ似たようなMethodBaseやMethodInfoというものが二つあるの?
ワテのこの質問に対するChatGPTさんの回答は以下の通り。
`System.Reflection.MethodBase`と`System.Reflection.MethodInfo`は、リフレクションを使用してメソッド情報を取得するためのクラスです。これらのクラスの違いは次の通りです。
- `System.Reflection.MethodBase`は、すべてのメソッドに共通する基本的な情報を提供します。このクラスからは、メソッドの名前、パラメーター、戻り値の型などの情報を取得することができます。
- `System.Reflection.MethodInfo`は、特定のメソッドに関する情報を提供します。具体的には、メソッドの戻り値の型やアクセス修飾子、パラメーター情報などを取得することができます。
一般的に、`System.Reflection.MethodBase`は`System.Reflection.MethodInfo`の基本クラスとして機能し、すべてのメソッドに共通する情報を提供します。一方、`System.Reflection.MethodInfo`は特定のメソッドに関する詳細な情報を提供する派生クラスです。
どちらのクラスを使用するかは、取得したいメソッド情報のレベルに応じて決定します。例えば、すべてのメソッドの基本情報を取得する場合は`MethodBase`を使用し、特定のメソッドの詳細情報を取得する場合は`MethodInfo`を使用します。
あなたが作成したサンプルコードでは、`System.Reflection.MethodBase.GetCurrentMethod()`が`MethodBase`のインスタンスを返し、`System.Reflection.MethodInfo.GetCurrentMethod()`が`MethodInfo`のインスタンスを返すため、それぞれの情報を出力しています。両方のクラスを利用することで、メソッドの基本情報と詳細情報の両方を取得することができます。
なるほど、そう言う事か。
staticクラスのstaticメソッド名を文字列で取得し実行したい場合は?
さて、冒頭の例では非staticなクラスの場合であったが、ワテの場合には静的クラスの静的メソッドも実行したい状況に遭遇した。
冒頭の例では、最初にクラスのインスタンスを作成しているので、そのまま流用するとエラーする。
以下のように書き換えたら上手く行った。
using System; //using System.Collections.Generic; using System.Linq; //using System.Text; //using System.Threading.Tasks; using System.Reflection; namespace ConsoleApplication1 { public static class MagicClass { private static int magicBaseValue = 9 ; // public MagicClass() // { // magicBaseValue = 9; // } public static int ItsMagic(int preMagic) { return preMagic *magicBaseValue; } } public class TestMethodInfo { public static void Main() { // Get the constructor and create an instance of MagicClass Type magicType = Type.GetType("ConsoleApplication1.MagicClass"); // ConstructorInfo magicConstructor = magicType.GetConstructor(Type.EmptyTypes); // object magicClassObject = magicConstructor.Invoke(new object[] { }); // Get the ItsMagic method and invoke with a parameter value of 100 MethodInfo magicMethod = magicType.GetMethod("ItsMagic"); // object magicValue = magicMethod.Invoke(magicClassObject, new object[] { 100 }); object magicValue = magicMethod.Invoke(null, new object[] { 100 }); Console.WriteLine("MethodInfo.Invoke() Example\n"); Console.WriteLine("MagicClass.ItsMagic() returned: {0}", magicValue); } }
その実行結果は以下の通り。
MethodInfo.Invoke() Example MagicClass.ItsMagic() returned: 900 続行するには何かキーを押してください . . .
まあ、要するにコンストラクタ作成を省略して、メソッド取得GetMethodと実行Invokeを行えば良いみたいだ。
本件を調査していて、StackoverFlowにも類似のサンプルが有ったので紹介したい。
StackoverFlowのサンプル
このクラスを例えば上記のnamespace ConsoleApplication1の中に追加して、
public static class TestStackoverFlow { public static void func() { string @namespace = "ConsoleApplication1"; var q = from t in Assembly.GetExecutingAssembly().GetTypes() where t.IsClass && t.Namespace == @namespace select t; q.ToList().ForEach(t => Console.WriteLine(t.Name)); } }
Main()にこの行を追加して
TestStackoverFlow.func();
実行すると、
MagicClass TestMethodInfo TextStackoverFlow <>c__DisplayClass3 続行するには何かキーを押してください . . .
と言う結果が表示される。
最初の三つは名前空間ConsoleApplication1の中にあるクラス名だ。
末尾の
<>c__DisplayClass3
は何かな?
分からんがまあいい。知っている人は教えて下さい。
まとめ
この記事ではC#に於いて、ソースコードの中にあるクラス名、メソッド、プロパティを文字列として取得する手法を紹介した。
具体的には、C#のSystem.Reflectionを使うと
- クラス名を文字列で指定してインスタンスを作成したり、
- そのクラス内のメソッドやプロパティを全取得したり、
- そのメソッドを実行したり出来る。
- staticクラスの場合も同様の処理が出来る。
- あるいは、実行時に関数名を取得するなども可能。
などを行う事が出来る。
まあ、便利な機能なので色々工夫すればいろんな小細工関数を作成する事が出来る。
ワテの場合には、C#プログラムを実行してその中の関数名を取得してデバッグ時にコンソールウインドウに書き出すなどに利用している。
でもまあ、ワテの経験で言うと、あまりヘンテコな小細工関数を作っても訳分からなくなる場合もあるので、あまりヘンテコな事は考えずに目的のプログラムを最短時間で完成させる事に専念するほうが良いだろう。
本を読む
アマゾンの評価も高い本だ。
C++ならこれが良いかも。
C#を学習するならドットネットの基礎を学習しておく事は最重要だ。
ワテみたいに良く分からないままReflectionを使っていてもあまり身に付かないぞ。
コメント