VB のたまご

作成日: 2016/01/01, 更新日: 2016/01/01


型変換に見る、通常処理とリフレクション処理の違い

通常処理の継承関係の調べ方

  •  ちょっと、型変換(キャスト)について調べていました。 今なんのインスタンスの型になっているのか調べる場合、私なら TypeOf キーワードを使った探し方か、TryCast メソッドを使った調べ方をします。

  •  例えば今、以下のようなインターフェースとベースクラスを継承した、いくつかの派生クラスがあるとします。
  • Interface Interface1
        Property Age As Integer
    End Interface
    
    Class BaseClass : Implements Interface1
        Public Property Age As Integer Implements Interface1.Age
        Public Property Name As String
        Public Sub New()
            Me.Age = 15
            Me.Name = "taro"
        End Sub
    End Class
    
    Class AppClass : Inherits BaseClass
    End Class
    
    Class SysClass : Inherits AppClass
    End Class
    
    Class WinClass : Inherits SysClass
    End Class
    

  •  これらのクラスを使って、継承関係を調べます。以下のサンプルです。
  • Dim base As New BaseClass
    Dim app As New AppClass
    
    ' その変数の型を見ているのではなく、
    ' その変数にセットされている型(インスタンス生成されたオブジェクト)を見て、チェックしている
    
    ' インスタンスの型を調べる
    If TypeOf base Is BaseClass Then Console.WriteLine("a1")
    If TypeOf base Is AppClass Then Console.WriteLine("a2")
    If TypeOf app Is BaseClass Then Console.WriteLine("a3")
    If TypeOf app Is AppClass Then Console.WriteLine("a4")
    Console.WriteLine("")
    
    Dim o1 As Object = Nothing
    o1 = New BaseClass : If TypeOf o1 Is BaseClass Then Console.WriteLine("b1")
    o1 = New BaseClass : If TypeOf o1 Is AppClass Then Console.WriteLine("b2")
    o1 = New AppClass : If TypeOf o1 Is BaseClass Then Console.WriteLine("b3")
    o1 = New AppClass : If TypeOf o1 Is AppClass Then Console.WriteLine("b4")
    Console.WriteLine("")
    
    ' キャスト可能か調べる
    Dim o2 As Object = Nothing
    o2 = TryCast(base, BaseClass) : If o2 IsNot Nothing Then Console.WriteLine("c1")
    o2 = TryCast(base, AppClass) : If o2 IsNot Nothing Then Console.WriteLine("c2")
    o2 = TryCast(app, BaseClass) : If o2 IsNot Nothing Then Console.WriteLine("c3")
    o2 = TryCast(app, AppClass) : If o2 IsNot Nothing Then Console.WriteLine("c4")
    Console.WriteLine("")
    
    ' 出力結果
    a1
    a3
    a4
    
    b1
    b3
    b4
    
    c1
    c3
    c4
    

  •  これって、その変数の「型」を見ているのではなくて、その変数にセットされている「インスタンス」を見ているので、 継承関係もチェックすることができるんですね。 ちなみに、継承元から継承先、継承先から継承元と、相互変換できない理由は、アップキャスト、ダウンキャストの扱いを知ることで分かります。 当サイトでもちらっと触れています


スポンサーリンク


リフレクション処理の継承関係の調べ方

  •  でもこれ、リフレクション経由でチェックしたい時、インスタンス生成しないと使えないやつなんですよね。 だから、今まで私は、ターゲットとなる型と GetType(xxx) を比較してチェックしていました。 でもこの方法だと、その変数の「型」を見る、というチェックなので、継承関係はチェックできません。

  • ' リフレクション経由の場合は、型で判断する
    ' しかし、「その型が何か」を見ているのであり、インスタンスを見ているわけではない
    
    ' 本来は、外部 dll 読込のつもり
    Dim asm = System.Reflection.Assembly.GetExecutingAssembly()
    Dim baseType = asm.GetType("WindowsApplication366.BaseClass")
    Dim appType = asm.GetType("WindowsApplication366.AppClass")
    Dim sysType = asm.GetType("WindowsApplication366.SysClass")
    Dim winType = asm.GetType("WindowsApplication366.WinClass")
    
    If baseType Is GetType(BaseClass) Then Console.WriteLine("d1")
    If baseType Is GetType(AppClass) Then Console.WriteLine("d2")
    If appType Is GetType(BaseClass) Then Console.WriteLine("d3a")
    If appType.BaseType Is GetType(BaseClass) Then Console.WriteLine("d3b")
    If appType Is GetType(AppClass) Then Console.WriteLine("d4")
    Console.WriteLine("")
    
    ' 出力結果
    d1
    d3b
    d4
    

  •  まぁインスタンス生成していないんだから、インスタンスではチェックできないのは当然です。 4パターン目のチェックで書いていますが、継承元を確認したい場合は、Type.BaseType プロパティと比較させます。

  •  そこで MSDN を見ていたところ、たどり着いたのが、Type.IsAssignableFrom メソッドでした。 このメソッドは、現在の型のインスタンスに対して、ターゲットとなる型のインスタンスを代入できるかどうかをチェックします。 これはつまり、その変数の「型」を見ているのではなくて、その変数にセットされている「インスタンス」を見ているということと、同じ扱いでいいのかなと見ています。

  • ' xxx に、yyy を代入できるかどうかを調べる
    ' この型に、指定の型を代入できるかどうか
    If baseType.IsAssignableFrom(baseType) Then Console.WriteLine("e1")
    If baseType.IsAssignableFrom(appType) Then Console.WriteLine("e2")
    If appType.IsAssignableFrom(baseType) Then Console.WriteLine("e3")
    If appType.IsAssignableFrom(appType) Then Console.WriteLine("e4")
    Console.WriteLine("")
    
    ' 出力結果
    e1
    e2
    e4
    

  •  この通り、自分と同じ型の他に、継承関係にある型もチェックして判断しています。


スポンサーリンク


調べていた理由、何がしたかったのか?

  •  実は、プラグイン機能の実現について、具体的な実装方法を調べていました。

  •  IsAssignableFrom メソッドを使ってチェックするとこういう使い方になります。
  • If base.GetType().IsAssignableFrom(appType) Then
        Dim obj = Activator.CreateInstance(appType)
        base = CType(obj, BaseClass)
        Console.WriteLine("age = {0}, name = {1}", base.Age, base.Name)
    End If
    
    ' 出力結果
    age = 15, name = taro
    

  •  で、実際にはこう書くかなぁ?とちょっと思っていました。例えばインターフェースを扱う場合、 IsAssignableFrom メソッドを使わないで、以下のように書きそうになる気がしています。
  • ' インターフェースだったら、こう書いてしまうかも
    Dim ifc As Interface1 = Nothing
    If appType.GetInterface("Interface1") IsNot Nothing Then
        Dim obj = Activator.CreateInstance(appType)
        ifc = CType(obj, Interface1)
        Console.WriteLine("age = {0}", ifc.Age)
    End If
    
    ' 出力結果
    age = 15
    

  •  ベースクラスを扱う場合でも、 IsAssignableFrom メソッドを使わないで、以下のように書きそうになる気がしています。
  • ' クラスだったら、こう書いてしまうかも
    If appType.BaseType Is GetType(BaseClass) Then
        Dim obj = Activator.CreateInstance(appType)
        base = CType(obj, BaseClass)
        Console.WriteLine("age = {0}, name = {1}", base.Age, base.Name)
    End If
    
    ' 階層が深すぎるクラスだったら、BaseType を必要な分だけつなげるのは煩わしいかも
    If sysType.BaseType.BaseType Is GetType(BaseClass) Then
        Dim obj = Activator.CreateInstance(sysType)
        base = CType(obj, BaseClass)
        Console.WriteLine("age = {0}, name = {1}", base.Age, base.Name)
    End If
    
    ' 出力結果
    age = 15, name = taro
    age = 15, name = taro
    

  •  で書いてみて思ったことは、ベースクラスを扱う場合、BaseType プロパティが1回だけ登場するのであれば、 私はまだこちらを使うかなと思ったのですが、2回以上となると、ちょっとしつこいかなと思いました。 何個目だったっけ?とかいちいち事前に調べておかないといけないのであれば、これからは IsAssignableFrom メソッドを使おうかなと考え直してしまいます。

  • ' その点、IsAssignableFrom は再帰的に見てくれるので、スマートかも
    If baseType.IsAssignableFrom(sysType) Then
        Dim obj = Activator.CreateInstance(sysType)
        base = CType(obj, BaseClass)
        Console.WriteLine("age = {0}, name = {1}", base.Age, base.Name)
    End If
    
    If baseType.IsAssignableFrom(winType) Then
        Dim obj = Activator.CreateInstance(winType)
        base = CType(obj, BaseClass)
        Console.WriteLine("age = {0}, name = {1}", base.Age, base.Name)
    End If
    
    ' 出力結果
    age = 15, name = taro
    age = 15, name = taro
    

  •  IsAssignableFrom メソッドを使うと、見やすいんですよね。横断している感じが良いです。 このメソッドって、まさにこのような状況のためにできた仕様なのかなとか、勝手に考えていました。

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


スポンサーリンク