読者です 読者をやめる 読者になる 読者になる

VB.NETでも遅延バインディングの呼び出し規則を確認したい

はじめに

遅延バインディングを使用したコードを眺めていた時にふと思ったのですが、遅延バインディングはどういう呼び出し規則でメソッドを呼び出しているのだろうと気になりました。

軽く調べてみたところ、言語仕様っぽいのは見つかりませんでした。 分かんないや!これは図書館に行かないとわかんないかも!

Roslynのコードを読めば分かりそうでありますが、あれを読むのは無理なので軽くテストで確認する程度にしておきましょう。

VBは遅延バインディングできるフレンズなんだね

単純な継承関係

Class Hoge

    Public Sub HogeHoge(arg As Object)
        Console.WriteLine("すごーい")
    End Sub

    Public Sub HogeHoge(arg As Fuga)
        Console.WriteLine("たのしー")
    End Sub

End Class
Class Fuga
End Class
Dim h As Object = New Hoge()
h.HogeHoge(new Object)
h.HogeHoge(New Fuga())
すごーい
たのしー

期待通りに呼び出されています。たのしー

亜空間型変換

Class Hoge

    Public Sub HogeHoge(arg As Integer)
        Console.WriteLine("すごーい")
        Console.WriteLine(arg)
    End Sub

End Class
Dim h As Object = New Hoge()
h.HogeHoge("123")
すごーい
123

さも当然かのように型変換をかけてきます。すごーい

拡大と縮小変換

Class Hoge

    Public Sub HogeHoge(arg As Fuga)
        Console.WriteLine("すごーい")
    End Sub

    Public Sub HogeHoge(arg As FugaFuga)
        Console.WriteLine("たのしー")
    End Sub

End Class
Class Fuga
End Class

Class FugaFuga
End Class

Class Piyo

    Public Shared Widening Operator CType(source As Piyo) As Fuga
        Console.WriteLine("拡大変換ができるフレンズなんだね")
        Return New Fuga()
    End Operator

    Public Shared Narrowing Operator CType(source As Piyo) As FugaFuga
        Console.WriteLine("縮小変換ができるフレンズなんだね")
        Return New FugaFuga()
    End Operator

End Class
Dim h As Object = New Hoge()
h.HogeHoge(New Fuga())
h.HogeHoge(New FugaFuga())
h.HogeHoge(New Piyo())
すごーい
たのしー
拡大変換ができるフレンズなんだね
すごーい

拡大変換と縮小変換では拡大変換が優先されます。へー

継承と拡大変換

Class Hoge

    Public Sub HogeHoge(arg As Fuga)
        Console.WriteLine("すごーい")
    End Sub

    Public Sub HogeHoge(arg As FugaFuga)
        Console.WriteLine("たのしー")
    End Sub

End Class
Class Fuga
End Class

Class FugaFuga
End Class

Class Piyo
    Inherits FugaFuga

    Public Shared Widening Operator CType(source As Piyo) As Fuga
        Console.WriteLine("拡大変換ができるフレンズなんだね")
        Return New Fuga()
    End Operator

End Class
Dim h As Object = New Hoge()
h.HogeHoge(New Fuga())
h.HogeHoge(New FugaFuga())

Try
    h.HogeHoge(New Piyo())
Catch ex As System.Reflection.AmbiguousMatchException
    Console.WriteLine(ex.Message)
End Try
すごーい
たのしー
これらの引数
    'Public Sub HogeHoge(arg As ConsoleApplication2.Fuga)':
        最も固有ではありません。
    'Public Sub HogeHoge(arg As ConsoleApplication2.FugaFuga)':
        最も固有ではありません。 に最も固有な、パブリック 'HogeHoge' がないため、オーバーロードの解決に失敗しました

継承関係と拡大変換ではオーバーロードを一意に決定できないっぽいので、呼び出しに失敗しSystem.Reflection.AmbiguousMatchExceptionがスローされます。へーきへーき!フレンズによって得意なこと違うから!

ジェネリック

Class Hoge(of T)

    Public Sub HogeHoge(arg As T)
        Console.WriteLine("すごーい")
    End Sub

    Public Sub HogeHoge(arg As Object)
        Console.WriteLine("わーい")
    End Sub

End Class
Class Fuga
End Class

Class FugaFuga
End Class
Dim h As Object = New Hoge(Of Fuga)()
h.HogeHoge(New Fuga())
h.HogeHoge(New FugaFuga())
h.HogeHoge(New Object())
すごーい
わーい
わーい

ジェネリックもちゃんといけます。すっごーい

まとめ

遅延バインディングは遅いと言われますが、実行時の型情報からオーバーロードの決定を毎回やっていたらそりゃ遅いよねってお話。

また、静的なオーバーロードコンパイル時に決定されるのに対して遅延バインディングの動的なオーバーロード解決は実行時に決定されるのでこんなこともできます。

Class Hoge

    Public sub HogeHoge(arg as IEnumerable)
        Console.WriteLine("たのしー")
    End sub
    
    Public Sub HogeHoge(arg As Hashtable)
        Console.WriteLine("すごーい")
    End Sub

    Public Sub HogeHoge(arg As ArrayList)
        Console.WriteLine("わーい")
    End Sub

End Class
Dim a As IEnumerable = new Hashtable()

Dim h As Object = New Hoge()
h.HogeHoge(a)

Dim ho = new Hoge()
ho.HogeHoge(a)
すごーい
たのしー

事前バインディングコンパイル時にどれが呼び出されるかがすでに決定されているので実際の型で呼び出し先のメソッドが変わることはありませんが、遅延バインディングは実行時に決定されるので実際の型で呼び出し先のメソッドを切り替えることができます。 乱用すればパターンマッチングっぽいことができるかもしれません。

でも、こう考えると遅延バインディング自分が何をやっているのかを完全に把握したうえで使うなら結構有用かもしれません。 ただ、コンパイル時ですら意味わかんないのに実行時まで不確定要素が出てくるとさすがにいろいろとアレなので弊社は遠慮しておきます。

たーのしー