.NET Framework 2.0でジェネリクスがサポートされたことに伴い、VB8(VB2005)以降ではVBでもジェネリクスを使用できるようになりました。 ジェネリクス(generics)とは何かを端的に述べると、ある機能を持つクラスやメソッドを、任意の型に対して適用できるようにするものです。

メソッド(プロシージャ)やクラスでは、何らかの動作や機能が定義されていて、その機能を指定された引数に対して適用することができます。 ジェネリックメソッド・ジェネリッククラスも何らかの機能が定義されている点は同様ですが、その機能を指定された型に対して適用できるようにするのがジェネリクスの機能です。 通常のメソッド・クラスで提供される機能は多くの場合何らかの型に依存することになりますが、ジェネリクスを使うと型に依存せず機能を提供することができるようになります。 様々な型に適用できる汎用的な機能を実装する際にジェネリクスを使うことができます。

例えば、コレクションクラスは複数のオブジェクトをまとめて管理し、追加・検索・削除などを行う機能を持っています。 ジェネリックなコレクションクラスでは、これらの機能を任意の型に対して適用できるようになっています。 そのため、ジェネリックなコレクションクラスを使うことで、String用のコレクション・Integer用のコレクションなど型に応じたコレクションクラスを個別に作成する必要がなくなります。

§1 ジェネリクスの例

§1.1 ジェネリックコレクション

ジェネリクスの有用な例として、ジェネリックコレクションがあります。 .NET FrameworkではSystem.Collections.Generic名前空間にさまざまなジェネリックなコレクションクラスが用意されています。

次の例は、ジェネリックな可変長リストであるListクラスを使って、String型のコレクションを作成し、要素を追加しています。

Imports System
Imports System.Collections.Generic

Module Sample
  Public Sub Main()
    ' String用のListを作成する
    Dim l As New List(Of String)

    ' Listに要素を追加する
    l.Add("Alice")
    l.Add("Bob")
    l.Add("Charlie")
  End Sub
End Module

ジェネリッククラスでは、クラス名(Of )とすることで、その型に型付けされたクラスを構築することができます。 例えば、ジェネリッククラスであるListの場合では、List(Of String)とすればString用のList、List(Of Integer)とすればInteger用のListを作成することができます。 Of句によってジェネリッククラスに与えられる型は型パラメータと呼ばれます。

型パラメータによって型付けされたクラスでは、その型、もしくはその型と互換性のある型拡大変換・暗黙の型変換が行える型のみが扱えます。

例えば、List(Of Integer)、つまりInteger型に片付けされたListでは、Integer型かInteger型に変換できる値(ShortやByte)のみが扱えます。 それ以外の型の場合を扱おうとすると、コンパイル時(Option Strict Onの場合)または実行時(Option Strict Offの場合)にエラーとなります。

Imports System
Imports System.Collections.Generic

Module Sample
  Public Sub Main()
    ' Integer用のListを作成する
    Dim l As New List(Of Integer)

    ' Listに要素を追加する
    Dim i As Integer = 1
    Dim s As Short = 2
    Dim str As String = "3"

    l.Add(i) ' Integer型の値はListの型と同じ型なので、追加できる
    l.Add(s) ' Short型の値もInteger型と互換性がある型なので、追加できる
    l.Add(str) ' String型の値はInteger型とは互換性のない型なので、追加できない
  End Sub
End Module

Listクラス以外にも、System.Collections.Generic名前空間にはさまざまなジェネリックコレクションクラスが用意されています。 詳しくはジェネリックコレクションを参照してください。

§1.2 ジェネリックメソッド

ジェネリクスは型の単位だけでなく、メソッド単位で適用することもできます。 ジェネリック型ではクラスや構造体などの型単位で型指定が行われますが、ジェネリックメソッドではメソッド単位で型指定が行われます。 このようなジェネリックメソッドの一例として、Array.Resizeメソッドがあります。

Array.Resizeメソッドは引数で与えられた配列のサイズを変更するものですが、配列の型を限定するために型パラメータが用いられます。 Integer型の配列を指定する場合はArray.Resize(Of Integer)、String型の配列の場合はArray.Resize(Of String)とします。

Imports System

Module Sample
  Public Sub Main()
    Dim intArray() As Integer = New Integer() {0, 1, 2, 3, 4}
    Dim strArray() As String = New String() {"0", "1", "2"}

    Array.Resize(Of Integer)(intArray, 3) ' Integer型配列の長さを3に変更する

    Array.Resize(Of String)(strArray, 5) ' String型配列の長さを5に変更する
  End Sub
End Module

このように、ジェネリックメソッドではメソッド名(Of )(引数1, 引数2, ...)とすることで、指定した型に限定されたメソッドを呼び出すことができます。

なお、コンパイラが適切な型を推定できる(型推論が行える)場合は、コンパイラが自動的に適切な型を選ぶため型パラメータの指定を省略することもできます。 例えば、先の例では引数に指定された配列の型から呼び出すべきジェネリックメソッドの型パラメータを推測できるため、次のように型パラメータの指定を省略して呼び出すこともできます。

Imports System

Module Sample
  Public Sub Main()
    Dim intArray() As Integer = New Integer() {0, 1, 2, 3, 4}
    Dim strArray() As String = New String() {"0", "1", "2"}

    Array.Resize(intArray, 3) ' Array.Resize(Of Integer)が呼び出される

    Array.Resize(strArray, 5) ' Array.Resize(Of String)が呼び出される
  End Sub
End Module


§2 ジェネリックメソッドの宣言

ここまでは既存のジェネリック型・ジェネリックメソッドを使用する例を見てきましたが、独自に定義することもできます。 ここでは二つの引数を入れ替えるジェネリックメソッドSwap(x, y)を作成することを考えます。 まずはジェネリクスを使わず通常のメソッドで実装すると、次のように引数の型ごとにメソッドを用意する必要があります。

Imports System

Module Sample
  ' 二つの引数の値を入れ替えるメソッド (Integer版)
  Sub Swap(ByRef x As Integer, ByRef y As Integer)
    Dim temp As Integer = x
    x = y
    y = temp
  End Sub

  ' 二つの引数の値を入れ替えるメソッド (String版)
  Sub Swap(ByRef x As String, ByRef y As String)
    Dim temp As String = x
    x = y
    y = temp
  End Sub

  Public Sub Main()
    Dim ix As Integer = 1, iy As Integer = 2
    Dim sx As String = "foo", sy As String = "bar"

    Console.WriteLine("{0} {1}", ix, iy)

    ' Integer版のSwapを呼び出す
    Swap(ix, iy)

    Console.WriteLine("{0} {1}", ix, iy)

    Console.WriteLine("{0} {1}", sx, sy)

    ' String版のSwapを呼び出す
    Swap(sx, sy)

    Console.WriteLine("{0} {1}", sx, sy)
  End Sub
End Module
実行結果
1 2
2 1
foo bar
bar foo

このSwapメソッドをジェネリックメソッドにすると次のようになります。 IntegerやStringといった具体的な型の代わりに、型パラメータTを引数の型とすることでどのような型に対しても呼び出せるようにします。 呼び出し側では型パラメータTに具体的な型を指定します。 この例では型推論によって型パラメータに指定されるべき型が推測できるので、型パラメータの指定を省略することもできます。

Imports System

Module Sample
  ' 二つの引数の値を入れ替えるメソッド (ジェネリック版)
  Sub Swap(Of T)(ByRef x As T, ByRef y As T)
    Dim temp As T = x
    x = y
    y = temp
  End Sub

  Public Sub Main()
    Dim ix As Integer = 1, iy As Integer = 2
    Dim sx As String = "foo", sy As String = "bar"

    Console.WriteLine("{0} {1}", ix, iy)

    ' Integer版のSwapを呼び出す
    Swap(Of Integer)(ix, iy)

    Console.WriteLine("{0} {1}", ix, iy)

    Console.WriteLine("{0} {1}", sx, sy)

    ' String版のSwapを呼び出す
    Swap(Of String)(sx, sy)

    Console.WriteLine("{0} {1}", sx, sy)
  End Sub
End Module
実行結果
1 2
2 1
foo bar
bar foo

このようにSub|Function メソッド名(Of 型パラメータ)(引数リスト)とすることでジェネリックメソッドを宣言することができます。

§3 ジェネリック型の宣言

クラスや構造体などをジェネリックにする場合は、Class|Structure 型名(Of 型パラメータ)とします。 クラス・構造体内では、具体的な型名の代わりに型パラメータで指定した型名を使用できるようになります。

Imports System

' 二つの値の対を表すジェネリック構造体
Structure Pair(Of T)
  Public X As T
  Public Y As T
End Structure

Module Sample
  Public Sub Main()
    ' Integer版のPair
    ' (フィールドXとYはInteger型になる)
    Dim p1 As New Pair(Of Integer)

    p1.X = 2
    p1.Y = 3

    ' String版のPair
    ' (フィールドXとYはString型になる)
    Dim p2 As New Pair(Of String)

    p2.X = "foo"
    p2.Y = "bar"
  End Sub
End Module

§4 ジェネリック型の継承

ジェネリック型を継承して新たに型を作成することもできます。 System.Collections.ObjectModel名前空間にはCollectionなどのさまざまな目的に使えるジェネリックなコレクションクラスが用意されているため、これを継承して独自のコレクション型を作成することもできます。

Imports System
Imports System.Collections.ObjectModel

' 正の整数のみを格納できるコレクション
Class PositiveNumberCollection
  ' Integer版のCollectionクラスを継承する
  Inherits Collection(Of Integer)

  Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As Integer)
    ' コレクションに設定しようとする値が負の場合はArgumentOutOfRangeExceptionをスローする
    If item < 0 Then Throw New ArgumentOutOfRangeException("値が負数")

    MyBase.SetItem(index, item)
  End Sub

  Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As Integer)
    ' コレクションに挿入しようとする値が負の場合はArgumentOutOfRangeExceptionをスローする
    If item < 0 Then Throw New ArgumentOutOfRangeException("値が負数")

    MyBase.InsertItem(index, item)
  End Sub
End Class

Module Sample
  Public Sub Main()
    Dim col As New PositiveNumberCollection()

    col.Add(0)
    col.Add(1)

    ' ArgumentOutOfRangeExceptionがスローされる
    col.Add(-1)
  End Sub
End Module

§5 型パラメータの制約

ジェネリック型・ジェネリックメソッドの宣言する際、Of句でOf Tとした場合、型パラメータTには任意の型を指定することができます。 型パラメータで指定できる型に制約を設けることもできます。 例えば値型もしくは参照型に限定したり、何らかのインターフェイスを実装している型に限定したり、といったことが出来ます。

制約について詳しくは制約で解説しています。