VB のたまご

作成日: 2015/12/24, 更新日: 2015/12/24


リフレクションを使って、各メンバーの詳細情報を取得しよう

  •  リフレクションについての第2回目です。 フィールド、プロパティ、メソッド等の各メンバーへのアクセス方法と操作方法を記載しています。
  • 第1回目はこちらです。


メンバー別の取得方法を知ろう

  •  前回は、リフレクションについてさらっと機能別に紹介しました。 動的実行するにしろ、メンバー情報を取得するにしろ、取得メソッド、設定メソッドを通して、処理を操作していくという特徴があります。 今回は、メンバー別の取得方法について、より詳しく説明したいと思います。


スポンサーリンク


インスタンスメンバーを取得する

  •  それではサンプルからです。ちょっと長いです。

  • 【解析側】取得メソッド
  • Imports System.Reflection
    
    Private Sub ShowMemberInfo(t As Type)
    
        Dim b As BindingFlags = BindingFlags.Public Or
                                BindingFlags.NonPublic Or
                                BindingFlags.Instance Or
                                BindingFlags.Static Or
                                BindingFlags.DeclaredOnly
    
        For Each member In t.GetMembers(b)
    
            Select Case True
    
                Case TypeOf member Is FieldInfo
                    Me.ShowFieldInfo(member)
    
                Case TypeOf member Is PropertyInfo
                    Me.ShowPropertyInfo(member)
    
                Case TypeOf member Is MethodInfo
                    Me.ShowMethodInfo(member)
    
            End Select
    
        Next
    
    End Sub
    
    Private Sub ShowFieldInfo(member As MemberInfo)
    
        Dim accessModifier As String = String.Empty
        Dim info As FieldInfo = CType(member, FieldInfo)
    
        Select Case True
            Case info.IsFamilyOrAssembly : accessModifier = "Protected Friend"
            Case info.IsPrivate : accessModifier = "Private"
            Case info.IsFamily : accessModifier = "Protected"
            Case info.IsAssembly : accessModifier = "Friend"
            Case info.IsPublic : accessModifier = "Public"
        End Select
    
        Console.WriteLine("kind = {0}, name = {1} : type = {2}, accessModifier = {3}", info.MemberType, info.Name, info.FieldType, accessModifier)
    
    End Sub
    
    Private Sub ShowPropertyInfo(member As MemberInfo)
    
        Dim accessModifier As String = String.Empty
        Dim info As PropertyInfo = CType(member, PropertyInfo)
        accessModifier = Me.GetAccessModifier(info)
    
        Console.WriteLine("kind = {0}, name = {1} : type = {2}, accessModifier = {3}", info.MemberType, info.Name, info.PropertyType, accessModifier)
    
    End Sub
    
    Private Sub ShowMethodInfo(member As MemberInfo)
    
        Dim accessModifier As String = String.Empty
        Dim info As MethodInfo = CType(member, MethodInfo)
    
        Select Case True
            Case info.IsFamilyOrAssembly : accessModifier = "Protected Friend"
            Case info.IsPrivate : accessModifier = "Private"
            Case info.IsFamily : accessModifier = "Protected"
            Case info.IsAssembly : accessModifier = "Friend"
            Case info.IsPublic : accessModifier = "Public"
        End Select
    
        Console.WriteLine("kind = {0}, name = {1} : type = {2}, accessModifier = {3}", info.MemberType, info.Name, info.ReturnType, accessModifier)
    
    End Sub
    
    Private Enum AccessModifiers
        None
        [Private]
        [Protected]
        [Friend]
        [Protected_Friend]
        [Public]
    End Enum
    
    Private Function GetAccessModifier(p As PropertyInfo) As String
    
        ' プロパティのアクセスレベルの判断は、
        ' ゲッター/セッターそれぞれのアクセス修飾子のうち、ゆるい方がプロパティのアクセス修飾子である、という判断をします。
        ' (StackOverflow の記事から)
    
        Dim readAccess As String = "None"
        Dim writeAccess As String = "None"
        If p.CanRead Then readAccess = Me.GetAccessModifierCore(p.GetMethod())
        If p.CanWrite Then writeAccess = Me.GetAccessModifierCore(p.SetMethod())
    
        ' 微調整
        If readAccess = "Protected Friend" Then readAccess = "Protected_Friend"
        If writeAccess = "Protected Friend" Then writeAccess = "Protected_Friend"
    
        ' いったん数値に変換して、大小比較
        Dim readModifier As AccessModifiers = AccessModifiers.None
        Dim writeModifier As AccessModifiers = AccessModifiers.None
    
        [Enum].TryParse(readAccess, readModifier)
        [Enum].TryParse(writeAccess, readModifier)
    
        ' 微調整戻しをして、返却
        If readModifier < writeModifier Then
            Return writeAccess.Replace("Protected_Friend", "Protected Friend")
        Else
            Return readAccess.Replace("Protected_Friend", "Protected Friend")
        End If
    
    End Function
    
    Private Function GetAccessModifierCore(m As MethodInfo) As String
    
        Dim x As String = String.Empty
    
        Select Case True
            Case m.IsFamilyOrAssembly : x = "Protected Friend"
            Case m.IsPrivate : x = "Private"
            Case m.IsFamily : x = "Protected"
            Case m.IsAssembly : x = "Friend"
            Case m.IsPublic : x = "Public"
        End Select
    
        Return x
    
    End Function
    
  •  変数1つに対して、名前や型の種類などのメタデータを扱っています。 続いて、呼び出し側です。

  • 【呼び出し側】の処理
  • ' チェック対象のクラス
    Public Class Person
    
        Private _Age As Integer = -1
        Public Property Age As Integer
            Get
                Return _Age
            End Get
            Set(value As Integer)
                _Age = value
            End Set
        End Property
    
        Public Sub ShowMessage(msg As String)
            Console.WriteLine(msg)
        End Sub
    
        Public Function Plus(i1 As Integer, i2 As Integer) As Integer
            Return i1 + i2
        End Function
    
    End Class
    
    ' 情報を取得します。(ロードイベントやボタンのクリックイベントなどに貼り付けてください)
    Dim t As Type = GetType(Person)
    Me.ShowMemberInfo(t)
    
    ' 出力結果
    kind = Method, name = get_Age : type = System.Int32, accessModifier = Public
    kind = Method, name = set_Age : type = System.Void, accessModifier = Public
    kind = Method, name = ShowMessage : type = System.Void, accessModifier = Public
    kind = Method, name = Plus : type = System.Int32, accessModifier = Public
    kind = Property, name = Age : type = System.Int32, accessModifier = Public
    kind = Field, name = _Age : type = System.Int32, accessModifier = Private
    

  •  前回記載した通り、リフレクションは、型のタイプ情報を元に操作していきます。まずこれありきです。 ShowMemberInfo メソッド内では、ループを使って、型のメンバー(フィールド、プロパティ、メソッド等)を取得しています。 メンバーを取得する命令は、GetMember(直接指定したいメンバー名)や、GetMembers メソッドを使います。 このように、リフレクションでは、単独のメンバーか、複数のメンバーを取得する命令が用意されていて、 フィールドやプロパティ、メソッドにも、同様の取得命令があります。

  •  GetMembers メソッドを呼び出す際、引数にバインド情報を渡しています。 指定しない場合、Public なメンバーのみに制限される他、ToString や GetType メソッド等の MS 側のメンバーも取得されてしまいます。 ここでは、Private なメンバーも取得対象にしているのと、こちら側で定義しているメンバーだけに制限をかけるために、バインド情報を設定しています。

  •  各メンバーにはそれぞれ専用の取得命令があります。フィールドなら FieldInfo, プロパティなら PropertyInfo, メソッドなら MethodInfo です。 また、FieldInfo, PropertyInfo, MethodInfo 等のそれぞれの専用クラスは、MemberInfo を継承して作成されています。 GetMembers メソッドで取得した際、継承元の MemberInfo に型変換されているので、元に戻すことで、細かく情報を取得することができるようになります。

  •  クラスは別々ですが、操作方法はだいたい統一しているので、なんとなく分かると思います。 1点、PropertyInfo のアクセス修飾子の取得方法についてです。PropertyInfo クラスには、アクセス修飾子を取得する命令がありません。 このことについて、StackOverflow に対処方法が載っていました。それは、ゲッターメソッド、セッターメソッドそれぞれのアクセス修飾子を比較して、 ゆるいアクセス修飾子の方が、プロパティのアクセス修飾子と判断できる。という内容でした。

  •  一応プチ講義しますと、プロパティは、フィールドとは違います。それは、値の取得、設定時に、メソッドのように値を編集できるところにあります。 これは、ビルド時に、プロパティ用の取得メソッド、設定メソッドとして自動生成されて、内部的に使用されます。 本サンプルでも、取得対象として表示されています。

  •  以上のことから、プロパティのメンバーとして、ゲッターメソッド、セッターメソッドを( MethodInfo として)取得することができるようになっていますので、 この情報を元に、プロパティのアクセス修飾子を独自に判定しています。


スポンサーリンク


共有メンバーを取得する

  •  続いては、共有メンバーを取得してみます。

  • 【呼び出し側】の処理
  • ' チェック対象のクラス
    Public Class Person2
    
        Private Shared _Age As Integer = -1
        Protected Friend Shared Property Age As Integer
            Get
                Return _Age
            End Get
            Private Set(value As Integer)
                _Age = value
            End Set
        End Property
    
        Protected Shared Sub ShowMessage(msg As String)
            Console.WriteLine(msg)
        End Sub
    
        Friend Shared Function Plus(i1 As Integer, i2 As Integer) As Integer
            Return i1 + i2
        End Function
    
    End Class
    
    ' 情報を取得します。(ロードイベントやボタンのクリックイベントなどに貼り付けてください)
    Dim t2 As Type = GetType(Person2)
    Me.ShowMemberInfo(t2)
    
    ' 出力結果
    kind = Method, name = get_Age : type = System.Int32, accessModifier = Protected Friend
    kind = Method, name = set_Age : type = System.Void, accessModifier = Private
    kind = Method, name = ShowMessage : type = System.Void, accessModifier = Protected
    kind = Method, name = Plus : type = System.Int32, accessModifier = Friend
    kind = Property, name = Age : type = System.Int32, accessModifier = Protected Friend
    kind = Field, name = _Age : type = System.Int32, accessModifier = Private
    

  •  解析側の処理は前節のものを使います。 ここでは、Shared なメンバーに加えて、アクセス修飾子をバラバラに設定していますが、うまく取得できていますね。


イベント、コンストラクタ、メソッドの引数メンバーを取得する

  •  続いて、イベント、メソッドの引数に関する情報です。

  • 【解析側】取得メソッド
  • Imports System.Reflection
    
    Private Sub ShowMemberInfo(t As Type)
    
        Dim b As BindingFlags = BindingFlags.Public Or
                                BindingFlags.NonPublic Or
                                BindingFlags.Instance Or
                                BindingFlags.Static Or
                                BindingFlags.DeclaredOnly
    
        For Each member In t.GetMembers(b)
    
            Select Case True
    
                Case TypeOf member Is ConstructorInfo
                    Me.ShowConstructorInfo(member)
    
                Case TypeOf member Is MethodInfo
                    Me.ShowMethodInfo(member)
    
                    Dim m As MethodInfo = CType(member, MethodInfo)
                    If 0 < m.GetParameters.Count Then
                        For Each p In m.GetParameters
                            Me.ShowParameterInfo(p)
                        Next
                    End If
    
                Case TypeOf member Is EventInfo
                    Me.ShowEventInfo(member)
    
            End Select
    
        Next
    
    End Sub
    
    Private Sub ShowConstructorInfo(member As MemberInfo)
    
        Dim accessModifier As String = String.Empty
        Dim info As ConstructorInfo = CType(member, ConstructorInfo)
    
        Select Case True
            Case info.IsFamilyOrAssembly : accessModifier = "Protected Friend"
            Case info.IsPrivate : accessModifier = "Private"
            Case info.IsFamily : accessModifier = "Protected"
            Case info.IsAssembly : accessModifier = "Friend"
            Case info.IsPublic : accessModifier = "Public"
        End Select
    
        Console.WriteLine("kind = {0}, name = {1} : type = {2}, accessModifier = {3}", info.MemberType, info.Name, "System.Void", accessModifier)
    
    End Sub
    
    Private Sub ShowMethodInfo(member As MemberInfo)
    
        Dim accessModifier As String = String.Empty
        Dim info As MethodInfo = CType(member, MethodInfo)
    
        Select Case True
            Case info.IsFamilyOrAssembly : accessModifier = "Protected Friend"
            Case info.IsPrivate : accessModifier = "Private"
            Case info.IsFamily : accessModifier = "Protected"
            Case info.IsAssembly : accessModifier = "Friend"
            Case info.IsPublic : accessModifier = "Public"
        End Select
    
        Console.WriteLine("kind = {0}, name = {1} : type = {2}, accessModifier = {3}", info.MemberType, info.Name, info.ReturnType, accessModifier)
    
    End Sub
    
    Private Sub ShowParameterInfo(p As ParameterInfo)
    
        Dim byvalOrByRef As String = "ByVal"
        If p.ParameterType.IsByRef Then byvalOrByRef = "ByRef"
    
        Console.WriteLine(vbTab & "number = {0}, argument passing = {1}, name = {2} : type = {3}", p.Position, byvalOrByRef, p.Name, p.ParameterType)
    
    End Sub
    
    Private Sub ShowEventInfo(member As MemberInfo)
    
        Dim accessModifier As String = String.Empty
        Dim info As EventInfo = CType(member, EventInfo)
    
        Console.WriteLine("kind = {0}, name = {1}", info.MemberType, info.Name)
    
    End Sub
    
  •  続いて、呼び出し側です。

  • 【呼び出し側】の処理
  • ' チェック対象のクラス
    Public Class Person3
    
        Public Event PersonalClick(sender As Object, e As EventArgs)
    
        Public Sub OnMyClick()
            RaiseEvent PersonalClick(Me, New EventArgs)
        End Sub
    
        Public Function Plus(i1 As Integer, i2 As Integer) As Integer
            Return i1 + i2
        End Function
    
    End Class
    
    ' 情報を取得します。(ロードイベントやボタンのクリックイベントなどに貼り付けてください)
    Dim t3 As Type = GetType(Person3)
    Me.ShowMemberInfo(t3)
    
    ' 出力結果
    kind = Method, name = __ENCAddToList : type = System.Void, accessModifier = Private
    	number = 0, argument passing = ByVal, name = value : type = System.Object
    kind = Method, name = add_PersonalClick : type = System.Void, accessModifier = Public
    	number = 0, argument passing = ByVal, name = obj : type = WindowsApplication353.Person3+PersonalClickEventHandler
    kind = Method, name = remove_PersonalClick : type = System.Void, accessModifier = Public
    	number = 0, argument passing = ByVal, name = obj : type = WindowsApplication353.Person3+PersonalClickEventHandler
    kind = Method, name = OnMyClick : type = System.Void, accessModifier = Public
    kind = Method, name = Plus : type = System.Int32, accessModifier = Public
    	number = 0, argument passing = ByVal, name = i1 : type = System.Int32
    	number = 1, argument passing = ByVal, name = i2 : type = System.Int32
    kind = Constructor, name = .cctor : type = System.Void, accessModifier = Private
    kind = Constructor, name = .ctor : type = System.Void, accessModifier = Public
    kind = Event, name = PersonalClick
    

  •  イベントに関する自動生成されたメンバーがいくつか見えますね。これは無視して構いません。 今回の処理の対象としている、コンストラクタや引数も取得できています。 このように、プログラムから必要な情報を取得して、動的に扱うことができます。

  •  ここで注意点があります。例えば、空の画面クラスを新規追加したとして、中身を見てみます。
  • Dim t As Type = GetType(Form5)
    Me.ShowMemberInfo(t)
    
    ' 出力結果
    (省略)
    

  •  出力結果には、自動生成されたメンバーのみが出力されています。ここに、例としてロードイベントを追加したとします。 もう一度、中身を見てみると、このようになります。
  • ' 出力結果
    ~省略~
    kind = Method, name = Form5_Load : type = System.Void, accessModifier = Private
    	number = 0, argument passing = ByVal, name = sender : type = System.Object
    	number = 1, argument passing = ByVal, name = e : type = System.EventArgs
    ~省略~
    

  •  イベントとして見ているメソッド(イベントハンドラ)は、メソッドの扱いとして出力されるんですね。イベントとして、ではないんです。 というより、イベントは出力されていませんが、それは自分で定義していないからです。 イベントを出力対象にするためには、バインド情報のうち、DeclaredOnly を削除することで出力対象にすることができます。 ただし、実際にやってみると、ロードイベントだけ出力されると思いきや、全イベントが出力されています。
  • ' 出力結果
    ~省略~
    kind = Event, name = AutoSizeChanged
    kind = Event, name = AutoValidateChanged
    kind = Event, name = HelpButtonClicked
    kind = Event, name = MaximizedBoundsChanged
    kind = Event, name = MaximumSizeChanged
    kind = Event, name = MarginChanged
    ~省略~
    

  •  つまり、イベントに関しては、定義している全イベントを抽出するのであって、使用中のイベントを抽出するのではない、という注意点があるんです。 イベント情報を取得したい場合は、メソッド情報の中から判断する必要があるんですね。

まとめ

  •  このように、リフレクションを使うと、プログラムの中からいろいろな情報を取得することができるようになります。 プラグイン操作の材料として、または、自分が作ったプログラムの特性を分析して(具体的にはアイディア次第ですが)、 さらなる技術力の向上につながれば幸いです。

  •  最後までこの記事を読んでいただき、ありがとうございました。


スポンサーリンク