前記事 で書いたように、VB はリフレクションなんて使わなくても多重ディスパッチ(マルチメソッド)できるらしいです…
http://www.infoq.com/news/2007/06/VB-Multiple-Dispatch
これがほんとならすごいなぁ~と思って、先日 C# で書いたサンプル とほぼ同じものを実装してみました(もちろんリフレクションを使わずに)。
じつは自分の VB に関する知識は VB6/VBA で止まっていて、.NET になってからの VB にさわるのはこれが初めてです。(VB.NET 暦1日、これが最初で最後かもしれませんw)
サンプルは VB 2005 で書きました。
Option Strict Off
MustInherit Class Matter
End Class
Class Spaceship : Inherits Matter
End Class
Class Asteroid : Inherits Matter
End Class
Class UFO : Inherits Matter
End Class
Partial Class Space
Shared Sub Collide(ByVal x As Spaceship, ByVal y As Spaceship)
Debug.Print("宇宙船と宇宙船が衝突")
End Sub
Shared Sub Collide(ByVal x As Spaceship, ByVal y As Asteroid)
Debug.Print("宇宙船と小惑星が衝突")
End Sub
… 一部省略 …
Shared Sub Collide(ByVal x As Matter, ByVal y As Spaceship)
Debug.Print("Unknownと宇宙船が衝突")
End Sub
Shared Sub Collide(ByVal x As Matter, ByVal y As Matter)
Debug.Print("UnknownとUnknownが衝突")
End Sub
End Class
Module MultipleDispatchSampleModule
Sub Main()
Dim x() = {New Spaceship(), New Spaceship(), New Asteroid(),…}
Dim r As New Random
For i As Integer = 0 To 9
Space.Collide(x(r.Next(x.Length)), x(r.Next(x.Length)))
Next
End Sub
End Module
で、実際に動かしてみると… すげ~~~、ちゃんと動いてる~
… ちゃんと実行時の型に基づいて実行時にディスパッチしています。
どうやらがせネタではなかったようです。
しかし、VB が正式に多重ディスパッチ(マルチメソッド)をサポートしているという話は聞いたことがなく、どうやら完全な多重ディスパッチではないようです。実際、上のサンプルコードが動くようになるまでにちょっとした苦労がありました…
最初は Matter などという抽象クラスを設けずにふつうに Object から派生して実装しようとしていました。で、Unknown との衝突も Object 型で表していたんですが(C# で書いたサンプルみたいに)、当然、Unknown 同士の衝突を次のように書いていました。
…
Shared Sub Collide(ByVal x As Object, ByVal y As Object)
Debug.Print("UnknownとUnknownが衝突")
End Sub
…
しかし、このすべての引数が Object 型のメソッドがあると、なぜかすべてこれにディスパッチされてしまうのです(いったいどういう仕様なんだろ?)。このディスパッチがコンパイル時に行われるものなのか実行時なのか、そこまではわかりませんでしたが(コンパイル時のような気がするけど…)、とにかくこのメソッドの存在のおかげで最初は「やっぱりがせネタでは?」と思ってしまいました。その後、上のメソッドをコメントアウトして動かしたのですが、Unknown 同士の衝突が System.Reflection.AmbiguousMatchException になってしまうため( ※ )、Matter という抽象クラスを導入したのです。(実際、サンプルコードに Matter を継承しない Antimatter クラスを作って配列に突っ込むと例外で落ちます。)
本物の多重ディスパッチであれば、こんなことは起こらないはずなのですが…。先にも書きましたが、VB が正式に多重ディスパッチをサポートしているという話は聞いたことがありません。おそらくなにかの副産物として多重ディスパッチができてしまうのでしょう…(VB6 以前からの遅延バインディングのサポートと .NET で新しく追加されたオーバーロードのサポートが重なってできた産物か?)
とはいえ、いくつか注意すべき点はあるものの、これが使えるとやはりとても便利です。これを使うことで設計がシンプルになるのであればふつうに使いたくなるんじゃないでしょうか…(ただし処理速度が遅くなることは知っておくべきです)。 設計の選択肢として仮想メソッド(Virtual)と非仮想メソッド(NonVirtual)を使い分けるように、オーバーロードとマルチメソッドを上手に使い分けたいですね。
※ 内部的にはやはりリフレクションを使って実現しているのでしょうね。
参照: