2014/08/02

【C#】TypeクラスやInvokeMember、GetTypeって何なの?

仕事で既存システムの改修をしているときに、さっぱり理解できない処理がでてきた。
それが「Type.InvokeMember」だった。

「Type」は型引数的なサムシングだろう。
「Invoke」はT.M.Revolution的なサムシングだろう。
「GetType」はT.M.Network的なサムシングだろう。

なんて漠然としてイメージしかなく、T.M.Revolutionがどうやって処理に組み込まれ、風になびいているかなんて全くもって理解できない。
『♪凍えそうな季節に君はバグをしーよー(仕様)と云うのー♪』というのであれば、身に覚えがなくはない。

「GetType」にしたってT.M.Networkが『♪Get Wild And Type♪』って歌ってるけど、Type.GetTypeはあってもType.GetWildなんて検索しても見つかりゃしない。

だから、どうしてもTypeクラスという現代社会の闇を理解できなかった。
と、ひと通り脱線したところで、このままじゃ仕事にならないので、Typeクラスについて調べてみた。




Typeクラス とは




型宣言を表します。型には、クラス型、インターフェイス型、配列型、値型、列挙型、型パラメーター、ジェネリック型定義、およびオープン構築ジェネリック型またはクローズ構築ジェネリック型があります。
引用元:Type クラス (System)
さっぱりわからん。さらに調べてみると……

「Type」はメタデータ(ライブラリのクラス名やメンバ名、アクセスレベル等の情報など)にアクセスするための手段で、プログラム実行時にメタデータにアクセスする機能をリフレクション(reflection)と呼ぶ。

一言で言うと、文字列で指定したクラス名からインスタンスを生成することができる機能。
ポリモーフィズム(多態性)を実現するために重要な機能だ。


具体的な例を出すと以下のようになる。
// 他のクラス
namespace MyNamespace
{
    class MyClass
    {
        public void MyMethod()
        {
            Console.WriteLine("呼び出された!");
        }
    }
}

// 実行するクラス
class Main
{
    // クラス名を設定して、そのクラスの型を取得する
    Type t = Type.GetType("MyNamespace.MyClass");

    // 取得したクラスの型を利用して、インスタンスを生成する
    // ※object型で返ってくるため、メンバメソッドを利用するためキャストする
    MyClass obj = (MyClass)Activator.CreateInstance(t);

    // インスタンスのメンバメソッドを呼び出す
    obj.MyMethod();

    // → 「呼び出された!」が出力される
}

最後の方は、Typeクラスの説明というより、GetTypeメソッドの話になってしまったがだいたいこんな感じ。




GetTypeメソッド とは



前述のリフレクションという機能を使って、指定したクラスなどの型情報を取得する。

GetTypeはTypeクラスのインスタンスを返し、そのインスタンスを使用し、その型に関するさまざまな情報にアクセスするための機能。
前述したサンプルプログラムを例にとると、GetTypeにより「MyNamespace.MyClass」の型(Typeインスタンス t)を取得し、CreateInstanceでその型(t)をインスタンス化する。そして、インスタンス化されたobjを用いて、「MyNamespace.MyClass」の情報にアクセス(obj.MyMethod)するという流れだ。



GetType と typeof の違い


型情報を取得するにはGetTypeの他にtypeof演算子がある。

GetTypeは「変数名.GetType()」のように実行時に“動的”に型情報を取得する。
typeofは「typeof(クラス名)」のように“静的”に型情報を取得する。


ポリモーフィズムのことを考えると、GetTypeのほうが良いかもしれない。
typeofはインスタンスを生成して云々もできるが、どちらかというとGetTypeで取得した型が指定の型と一致しているか( if(obj.GetType() == typeof(MyClass))みたいな使い方が多い気がする。

また、巷ではGetTypeとtypeofの性能について議論があるようだが、よほど性能にシビアな現場でないかぎり、気にする必要はないだろう。

参考 : c# - typeof(T) vs. Object.GetType() performance - Stack Overflow




InvokeMemberメソッド とは



Type.InvokeMemberメソッドを使用することで、インスタンスの生成、メソッドの呼び出し、プロパティの設定・取得の全てができる。

最初の例に出した「Activator.CreateInstance」のくだりが、これひとつでできるというわけだ。

英語のInvokeには「発動する、呼び出す、引き起こす」という意味がある。
直訳すれば、メンバメソッドやメンバ変数を呼び出すためのメソッドとなる。

実際に使うと以下のようになる。
// MyClassの型情報を取得
Type t = Type.GetType("MyNamespace.MyClass");

// 型情報を元にMyClassのインスタンスを生成
object o = t.InvokeMember(
                null
                , BinfingFlags.CreateInstance
                , null
                , null
                , new object[] {}
                );

// メソッド呼び出し(戻り値なし)
t.InvokeMember(
        "MyMethod"
        , "BindingFlags.InvokeMethod"
        , null
        , null
        , new object[] {}
        );

// メソッド呼び出し(戻り値あり) 2つの引数を合計を返すメソッド
int num = (int)t.InvokeMember(
                "MySum"
                , BindingFlags.InvokeMethod
                , null
                , null
                , new int[] {10, 20}
                );

public abstract Object InvokeMember(
            string name,
            BindingFlags invokeAttr,
            Binder binder,
            Object target,
            Object[] args,
            ParameterModifier[] modifiers,
            CultureInfo culture,
            string[] namedParameters
)


  • name : コンストラクタ、メソッド、プロパティなどの名前
  • invokeAttr : 検索方法(nameの名前がメソッドなのかプロパティなのかコンストラクタなのか指定する)
  • binder : あまり使わないからよくわからない(argsに名前付き引数を渡すときに値をバインド(紐付け)するヤツ)
  • target : 制御対象のインスタンスを渡すことで、そのメンバなどが参照できる
  • args : 呼び出すメンバ(name)に渡す引数配列



具体的な使用例



冒頭で既存システムの改修時に出てきたと書いたが、そのシステムの内容をそのまま載せると情報漏洩・セキュリティ問題で懲戒処分になって、生活できなくなってしまう。

そのため、今テキトーに考えたシステムを例にあげる。


システム概要

  • 各業務画面に「申請」処理が実装されている
  • 伝票一覧画面では、各業務画面でつくられた伝票の一覧が表示される
  • 伝票一覧画面で、対象伝票を選択し「一括申請」処理を行うと、各業務画面で実装されている「申請」処理が実行される

処理内容

  1. 伝票一覧画面で選択された伝票をループでまわす
  2. それぞれの伝票(各業務画面)のインスタンスを生成する
  3. インスタンスのメンバメソッド「Shinsei」を呼び出し「申請」処理を行う


public void DoIkkatsuShinsei()
{
    foreach (MyForm form in MeisaiList)
    {
        // 名前空間+クラス名の文字列を生成
        string VOName = String.Empty;
        VOName = "MyNamespace.VO.";
        VOName += form.pgid;

        // 上記で作成した文字列でインスタンスを生成
        Type typeVO = Type.GetType(VOName);
        object vo = Activator.CreateInstance(typeVO, new object[] {});

        // プロパティ「伝票No」にMyForm.DenpyoNoを設定する
        typeVO.InvokeMember(
                "DenpyoNo"
                , BindingFlags.SetProperty
                , null
                , vo
                , new object[] {form.DenpyoNo}
                );

        // メソッド「Shinsei」を呼び出し、申請処理を行う
        object result = typeVO.InvokeMember(
                            "Shinsei"
                            , BindingFlags.InvokeMethod
                            , null
                            , vo
                            , new object[] {context}
                            );
    }
}


こんな感じでポリモーフィズムを利用して更新処理が行える。

まだ未知の領域ではあるが、少しずつ理解していきたい。


以上

written by @bc_rikko

0 件のコメント :

コメントを投稿