VB.NETでもリフレクションで(途中略)メソッドを実行したい

タイトル補足

特に需要なんてものは無かったんですが、自作の属性をメソッドに付与してその属性が付与されたメソッドをリフレクションを用いて実行時に取得して一括実行するにはどうすればいいのか気になったので調べました。

流れ

大体以下の流れで処理を記述しています。

  1. 自作属性を定義
  2. 自作属性をメソッドに付与
  3. クラス内のメソッドリストを取得
  4. メソッドリストの属性を検査
  5. 自作属性が付与されていたら実行

とりあえずコードを乗っけていい感じに説明的なアレをアレします。

動的

Imports System.Reflection

Module Entry

    Sub Main()
        Dim targetA = New TargetClass("A")
        Dim targetB = New TargetClass("B")

        targetA.RunAll()
        targetB.RunAll()
    End Sub

End Module

<AttributeUsage(AttributeTargets.Method, AllowMultiple:=False)>
Public Class AutoRunAttribute
    Inherits Attribute

    Public Property Rank As Integer

    Public Sub New()
        Me.Rank = 0
    End Sub
End Class

Public Class TargetClass

    Private _name As String

    Public Sub New(name As String)
        _name = name
    End Sub

    <AutoRun()>
    Public Sub MethodA()
        Console.WriteLine("{0}:MethodA", _name)
    End Sub

    <AutoRun(Rank:=-10)>
    Public Sub MethodB()
        Console.WriteLine("{0}:MethodB", _name)
    End Sub

    <AutoRun(Rank:=10)>
    Public Sub MethodC()
        Console.WriteLine("{0}:MethodC", _name)
    End Sub

    <AutoRun>
    Public Sub MethodD()
        ' せっかくなのでリフレクションを用いて自分自身の名前を取得する
        Dim myName = MethodBase.GetCurrentMethod().Name
        Console.WriteLine("{0}:{1}", _name, myName)
    End Sub

    Public Sub MethodE()
        Console.WriteLine("無関係なメソッド")
    End Sub

    Public Sub RunAll()
        Dim method = GetType(TargetClass).GetMethods()
        Dim runnable = New List(Of RunMethodInfo)()

        For Each it In method
            Dim attr = Attribute.GetCustomAttributes(it)
            For Each j In attr
                Dim a = TryCast(j, AutoRunAttribute)
                If a Is Nothing Then Continue For
                runnable.Add(New RunMethodInfo With {.Method = it, .Rank = a.Rank})
            Next
        Next

        For Each it In runnable.OrderBy(Function(a) a.Rank)
            it.Method.Invoke(Me, Nothing)
        Next

    End Sub

    Private Class RunMethodInfo
        Public Property Method As MethodInfo
        Public Property Rank As Integer
    End Class

End Class

自作属性はSystem.Attributeを継承して実装します。

属性に対する属性はSystem.AttributeUsageで指定します。

属性の付与する対象はSystem.AttributeTargets列挙体を用いて指定をします。 対象はOr演算子で複数指定することができますのでお好きなだけ対象を設定できます。

自作属性を同じ対象に複数付与できるかどうかはAllowMultipleプロパティで指定します。 今回は複数付与されると厄介なのでFalseを指定してます。

自作した属性ですが、今回は実行の優先順位を表すRankプロパティを設けています。 Rankの値が低いほど先に実行されます。

メソッドに属性を付与する方法ですが、まぁこれはコードを見てもらったほうがわかると思います。

本題がRunAllメソッドですが、

  1. GetTypeで自分自身のクラスのType((正確には)のサブクラス)インスタンスをを取得
  2. Type.GetMethodsメソッドでアクセス修飾子がPublicなメソッドをSystem.Reflection.MethodInfoとして全て取得
  3. System.Attribute.GetCustomAttributesメソッドでMethodInfoインスタンスから属性をぶっこぬく
  4. ぶっこぬいてきた属性が自作属性かチェック
  5. 自作属性だったらとりあえずリストに格納
  6. ランクに応じてソートしてMethodinfo.Invokeで実行

って流れです。

ちなみに実行結果はこんな感じです。 属性を設定していないMethodEは実行されていません。

A:MethodB
A:MethodA
A:MethodD
A:MethodC
B:MethodB
B:MethodA
B:MethodD
B:MethodC

実のところ発端は引数を取らないメソッドを細かく分割して定義したあげく一つのメソッドで細切れにしたメソッドをズラーっと列挙して呼び出しているコードを見てもうちょっとスマートに出来ないかなと思ったからです。 でもよくよく考えたらそもそもの設計からして間違ってる気もしますよね。

とまぁフレームワークを自作する時以外は使わなそうなお話です。 メタプログラミングを駆使すればエキセントリックなコードをいくらでも書けるので無秩序にプロジェクトにぶち込んでメンテナンス性最悪なコードを大量に生み出したいところです。

おわりのはじまり