2013-10-06T00:11:56の更新内容

programming/vb.net/generics/index.wiki.txt

current previous
1,274 1,392
 
${smdncms:title,ジェネリクス}
${smdncms:title,ジェネリクス}
~
${smdncms:keywords,ジェネリクス,ジェネリック,System.Collections.Generic}
${smdncms:keywords,ジェネリクス}
 
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,visual basic}
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,visual basic}
-
#navi(..)
-
.NET Frameworkでジェネリクスがサポートされたことに伴い、VB8でもジェネリクスを使用できるようになりました。
-
#googleadunit
-

          
-
*ジェネリクスの例
-
**System.Collections.Generic.Stack
-
まずはジェネリクスを使った例として、新たに追加されたSystem.Collections.Generic.Stackクラスを使ってみます。
-

          
-
#code(vb){{
-
Imports System
-
Imports System.Collections.Generic
-

          
-
Class GenericStackSample
 

        

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

        

        
~
メソッド([[programming/vb.net/basics/07_procedure]])やクラスでは、何らかの動作や機能が定義されていて、その機能を''指定された引数に対して適用''することができます。 ジェネリックメソッド・ジェネリッククラスも何らかの機能が定義されている点は同様ですが、その機能を''指定された型に対して適用''できるようにするのがジェネリクスの機能です。 通常のメソッド・クラスで提供される機能は多くの場合何らかの型に依存することになりますが、ジェネリクスを使うと型に依存せず機能を提供することができるようになります。 様々な型に適用できる汎用的な機能を実装する際にジェネリクスを使うことができます。
        Dim s As Stack(Of Integer) = New Stack(Of Integer)
 

        

        
~
例えば、コレクションクラスは複数のオブジェクトをまとめて管理し、追加・検索・削除などを行う機能を持っています。 ジェネリックなコレクションクラスでは、これらの機能を任意の型に対して適用できるようになっています。 そのため、ジェネリックなコレクションクラスを使うことで、String用のコレクション・Integer用のコレクションなど型に応じたコレクションクラスを個別に作成する必要がなくなります。
        s.Push(1)
-
        s.Push(2)
-
        s.Push(3)
-
        s.Push(4)
-
        s.Push(5)
-
        's.Push("test") <- Option Strict On disallows implicit conversions from 'String' to 'Integer'.
 

        

        
~
#googleadunit
        While 0 < s.Count
-

          
-
            Console.WriteLine(s.Pop())
-

          
-
        End While
-

          
-
    End Sub
-

          
-
End Class
-
}}
-

          
-
#prompt(実行結果){{
-
5
-
4
-
3
-
2
-
1
-
}}
 

        

        
~
*ジェネリクスの例
ここで使用したStackクラスは、System.Collections.Generic.Stackであり、System.Collections.Stackではありません。 ジェネリックではないSystem.Collections.Stackでは、全ての引数はObject型になっていたので、どのような型の値でも自由にスタックに入れることが出来ましたが、ジェネリックな方の Stack(System.Collections.Generic)ではパラメータに使用される型が厳密に決定されます。 そのため、その型をTとすると、TとTに型変換可能な型の値しかスタックに入れることが出来なくなります。 この例のように、パラメータに使用する型はOfキーワードを使用して指定できます。
+
**ジェネリックコレクション
+
ジェネリクスの有用な例として、ジェネリックコレクションがあります。 .NET Frameworkでは&msdn(netfx,ns,System.Collections.Generic){System.Collections.Generic名前空間};にさまざまなジェネリックなコレクションクラスが用意されています。
 

        

        
~
次の例は、ジェネリックな可変長リストである[[Listクラス>programming/netfx/collections/2_generic_1_list]]を使って、String型のコレクションを作成し、要素を追加しています。
**System.Collections.Generic.List
-
System.Collections.Generic.Listクラスは、厳密に型定義されたジェネリックなArrayListと言えるようなクラスです。
 

        

        
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
 
Imports System.Collections.Generic
Imports System.Collections.Generic
 

        

        
~
Module Sample
Class GenericListSample
~
  Public Sub Main()

          
~
    ' String用のListを作成する
    Shared Sub Main()
~
    Dim l As New List(Of String)

          
-
        Dim l As List(Of String) = New List(Of String)
-

          
-
        l.Add("Visual")
-
        l.Add("Basic")
-
        l.Add("2005") ' ← l.Add(2005)では型がStringではないためNG
-
        l.Add("Express")
-
        l.Add("Edition")
-
        l.Add("Beta")
-

          
-
        For Each s As String In l
-

          
-
            Console.Write(s)
-
            Console.Write(" ")
 

        

        
~
    ' Listに要素を追加する
        Next
~
    l.Add("Alice")

          
~
    l.Add("Bob")
    End Sub
~
    l.Add("Charlie")

          
~
  End Sub
End Class
+
End Module
 
}}
}}
 

        

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

          
Visual Basic 2005 Express Edition Beta 
~
型パラメータによって型付けされたクラスでは、その型、もしくはその型と[[互換性のある型>programming/netfx/tips/check_type_isassignablefrom_issubclassof]]・[[拡大変換・暗黙の型変換>programming/netfx/conversion/0_basetype]]が行える型のみが扱えます。
}}
 

        

        
~
例えば、``List(Of Integer)``、つまりInteger型に片付けされたListでは、Integer型かInteger型に変換できる値(ShortやByte)のみが扱えます。 それ以外の型の場合を扱おうとすると、コンパイル時(Option Strict Onの場合)または実行時(Option Strict Offの場合)にエラーとなります。
ただ、単なるArrayListのジェネリクス版に止まらず、色々な機能を持ったメソッドが提供されています。 例えば、上記のコードと同じ結果を出力するコードを別の書き方で書いてみます。
 

        

        
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
 
Imports System.Collections.Generic
Imports System.Collections.Generic
 

        

        
~
Module Sample
Class GenericListForEachSample
~
  Public Sub Main()

          
~
    ' Integer用のListを作成する
    Shared Sub Main()
~
    Dim l As New List(Of Integer)

          
-
        Dim l As List(Of String) = New List(Of String)
 

        

        
~
    ' Listに要素を追加する
        l.Add("Visual")
~
    Dim i As Integer = 1
        l.Add("Basic")
~
    Dim s As Short = 2
        l.Add("2005")
~
    Dim str As String = "3"
        l.Add("Express")
-
        l.Add("Edition")
-
        l.Add("Beta")
 

        

        
~
    l.Add(i) ' Integer型の値はListの型と同じ型なので、追加できる
        l.ForEach(New Action(Of String)(AddressOf WriteToConsole))
~
    l.Add(s) ' Short型の値もInteger型と互換性がある型なので、追加できる

          
~
    l.Add(str) ' String型の値はInteger型とは互換性のない型なので、追加できない
        ' 上記の一行は以下のコードと同義
~
  End Sub

          
~
End Module
        'For Each s As String In l
-

          
-
        '    WriteToConsole(s)
-

          
-
        'Next
-

          
-
    End Sub
-

          
-
    Shared Sub WriteToConsole(ByVal s As String)
-

          
-
        Console.Write(s)
-
        Console.Write(" ")
-

          
-
    End Sub
-

          
-
End Class
-
}}
-

          
-
#prompt(実行結果){{
-
Visual Basic 2005 Express Edition Beta 
 
}}
}}
 

        

        
~
Listクラス以外にも、System.Collections.Generic名前空間にはさまざまなジェネリックコレクションクラスが用意されています。 詳しくは[[programming/netfx/collections/2_generic_0_abstract]]を参照してください。
Actionデリゲートは、''Delegate Sub Action(Of T)(Ojb As T)''と定義された、任意の型の引数を一つ取り、値を返さないメソッドを表す汎用的なデリゲートです。 List.ForEach()メソッドはこの Actionデリゲートを引数に取ります。 ForEach()メソッドでは、List内の全ての要素がこの引数に渡されたメソッドに対して渡されます。 いちいちFor Eachステートメントを使わなくても、汎用的な処理を書いたメソッドを用意しておくだけでFor Eachと同様な処理を行うことが出来ます。 ちなみに、分かりやすくデリゲートインスタンスを作成して表記しましたが、以下のように簡略化する事も出来ます。
 

        

        
~
**ジェネリックメソッド
#code(vb){{
~
ジェネリクスは型の単位だけでなく、メソッド単位で適用することもできます。 ジェネリック型ではクラスや構造体などの型単位で型指定が行われますが、ジェネリックメソッドではメソッド単位で型指定が行われます。 このようなジェネリックメソッドの一例として、&msdn(netfx,member,System.Array.Resize){Array.Resizeメソッド};があります。
l.ForEach(New Action(Of String)(AddressOf WriteToConsole))
-
-
l.ForEach(AddressOf WriteToConsole)
-
}}
 

        

        
~
Array.Resizeメソッドは引数で与えられた配列のサイズを変更するものですが、配列の型を限定するために型パラメータが用いられます。 Integer型の配列を指定する場合は``Array.Resize(Of Integer)``、String型の配列の場合は``Array.Resize(Of String)``とします。
同じようなメソッドで、Predicateデリゲートを引数に取るRemoveAll()というメソッドがあります。 こちらは''Delegate Function Predicate(Of T)(Ojb As T) As Boolean''と定義された、任意の型の引数を一つ取り、Boolean型の値を返すメソッドを表すデリゲートです。 RemoveAll()メソッドは、引数に条件式を取り、リストからその条件式に適合する要素を全て取り除くメソッドと言えます。 この例では大文字のEで始まる要素を取り除いてから表示しています。
 

        

        
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
-
Imports System.Collections.Generic
 

        

        
~
Module Sample
Class GenericListRemoveAllSample
+
  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に変更する
    Shared Sub Main()
 

        

        
~
    Array.Resize(Of String)(strArray, 5) ' String型配列の長さを5に変更する
        Dim l As List(Of String) = New List(Of String)
~
  End Sub

          
~
End Module
        l.Add("Visual")
-
        l.Add("Basic")
-
        l.Add("2005")
-
        l.Add("Express")
-
        l.Add("Edition")
-
        l.Add("Beta")
-

          
-
        l.RemoveAll(AddressOf IsStartWithE)
-

          
-
        l.ForEach(AddressOf WriteToConsole)
-

          
-
    End Sub
-

          
-
    Shared Sub WriteToConsole(ByVal s As String)
-

          
-
        Console.Write(s)
-
        Console.Write(" ")
-

          
-
    End Sub
-

          
-
    Shared Function IsStartWithE(ByVal s As String) As Boolean
-

          
-
        Return s.StartsWith("E")
-

          
-
    End Function
-

          
-
End Class
 
}}
}}
 

        

        
~
このように、ジェネリックメソッドでは``&var{メソッド名};(Of &var{型};)(&var{引数1};, &var{引数2};, ...)``とすることで、指定した型に限定されたメソッドを呼び出すことができます。
#prompt(実行結果){{
-
Visual Basic 2005 Beta 
-
}}
-

          
-
また、FindAll()メソッドを使うと引数に取った条件式に適合する要素をListとして返してくれます。 このメソッドと先ほどのコードを元にし、「リスト内の要素のうち、Eで始まる文字列をすべて並べて表示する」ということをする場合は、次のようにするだけで出来ます。
-

          
-
#code(vb){{
-
l.FindAll(AddressOf IsStartWithE).ForEach(AddressOf WriteToConsole)
-
}}
 

        

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

        

        
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
-
Imports System.Collections
-

          
-
Class GenericType(Of T)
-

          
-
    Private m_Value As T
-

          
-
    Public Sub New(ByVal value As T)
-

          
-
        m_Value = value
-

          
-
    End Sub
-

          
-
    Public Overrides Function ToString() As String
 

        

        
~
Module Sample
        Return String.Format("{0}({1})", m_Value.ToString(), m_Value.GetType().FullName)
+
  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)が呼び出される
    End Function
 

        

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

          
~
End Module
Class GenericTypeSample
~
}}

          
-
    Shared Sub Main()
-

          
-
        Dim arr As New ArrayList()
-

          
-
        arr.Add(New GenericType(Of Integer)(123))
-
        arr.Add(New GenericType(Of Double)(123.4))
-
        arr.Add(New GenericType(Of String)("moji moji"))
-
        arr.Add(New GenericType(Of Boolean)(True))
-

          
-
        For Each val As Object In arr
-

          
-
            Console.WriteLine(val)
 

        

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

        

        
~
#code(vb){{
    End Sub
+
Imports System
 

        

        
~
Module Sample
End Class
+
  ' 二つの引数の値を入れ替えるメソッド (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
 
}}
}}
 

        

        
 
#prompt(実行結果){{
#prompt(実行結果){{
~
1 2
123(System.Int32)
~
2 1
123.4(System.Double)
~
foo bar
moji moji(System.String)
~
bar foo
True(System.Boolean)
 
}}
}}
 

        

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

        

        
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
 

        

        
~
Module Sample
Class GenericMethodSample
~
  ' 二つの引数の値を入れ替えるメソッド (ジェネリック版)

          
~
  Sub Swap(Of T)(ByRef x As T, ByRef y As T)
    Shared Sub Main()
~
    Dim temp As T = x

          
~
    x = y
        Dim intA As Integer = 3
~
    y = temp
        Dim intB As Integer = 5
~
  End Sub

          
-
        Console.WriteLine("{0}, {1}", intA, intB)
-

          
-
        Swap(Of Integer)(intA, intB)
-

          
-
        Console.WriteLine("{0}, {1}", intA, intB)
 

        

        
+
  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)
        Dim strA As String = "VB"
-
        Dim strB As String = "2005"
 

        

        
~
    ' Integer版のSwapを呼び出す
        Console.WriteLine("{0}, {1}", strA, strB)
+
    Swap(Of Integer)(ix, iy)
 

        

        
~
    Console.WriteLine("{0} {1}", ix, iy)
        Swap(Of String)(strA, strB)
 

        

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

        

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

        

        
~
    Console.WriteLine("{0} {1}", sx, sy)
    Shared Sub Swap(Of T)(ByRef a As T, ByRef b As T)
~
  End Sub

          
~
End Module
        Dim temp As T = a
-

          
-
        a = b
-

          
-
        b = temp
-

          
-
    End Sub
-

          
-
End Class
 
}}
}}
 

        

        
 
#prompt(実行結果){{
#prompt(実行結果){{
~
1 2
3, 5
~
2 1
5, 3
~
foo bar
VB, 2005
~
bar foo
2005, VB
 
}}
}}
 

        

        
~
このように``Sub|Function &var{メソッド名};(Of &var{型パラメータ};)(&var{引数リスト};)``とすることでジェネリックメソッドを宣言することができます。
この例のように、コンパイラが呼び出すべき型を推測できる場合は、Ofを省略して呼び出しを次のように記述することもできます。
-

          
-
#code(vb){{
-
Shared Sub Main()
-

          
-
    Dim intA As Integer = 3
-
    Dim intB As Integer = 5
-

          
-
    Swap(intA, intB)
-

          
-
    Dim strA As String = "VB"
-
    Dim strB As String = "2005"
-

          
-
    Swap(strA, strB)
 

        

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

        

        
~
#code(vb){{
Shared Sub Swap(Of T)(ByRef a As T, ByRef b As T)
~
Imports System

          
-
    (省略)
 

        

        
~
' 二つの値の対を表すジェネリック構造体
End Sub
+
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
 
}}
}}
 

        

        
+

          
 
*ジェネリック型の継承
*ジェネリック型の継承
~
ジェネリック型を継承して新たに型を作成することもできます。 &msdn(netfx,ns,System.Collections.ObjectModel){System.Collections.ObjectModel名前空間};には[[Collection>programming/netfx/collections/3_objectmodel_1_collection#Collection]]などのさまざまな目的に使えるジェネリックなコレクションクラスが用意されているため、これを継承して独自のコレクション型を作成することもできます。
System.Collections.ObjectModel名前空間に存在するジェネリックなコレクションクラスCollectionを継承して、独自のコレクションクラスを作成します。 この例で示すように、Collectionの継承の際に型を指定するだけで、簡単に特定の型専用のコレクションクラスが作成できます。
 

        

        
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
 
Imports System.Collections.ObjectModel
Imports System.Collections.ObjectModel
 

        

        
~
' 正の整数のみを格納できるコレクション
MustInherit Class Animal
~
Class PositiveNumberCollection

          
~
  ' Integer版のCollectionクラスを継承する
    Public MustOverride Sub Say()
~
  Inherits Collection(Of Integer)

          
-
End Class
-

          
-
Class Cat
-

          
-
    Inherits Animal
-

          
-
    Public Overrides Sub Say()
-

          
-
        Console.WriteLine("meow")
-

          
-
    End Sub
 

        

        
~
  Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As Integer)
End Class
~
    ' コレクションに設定しようとする値が負の場合はArgumentOutOfRangeExceptionをスローする

          
~
    If item < 0 Then Throw New ArgumentOutOfRangeException("値が負数")
Class Dog
-

          
-
    Inherits Animal
-

          
-
    Public Overrides Sub Say()
-

          
-
        Console.WriteLine("bow wow")
-

          
-
    End Sub
-

          
-
End Class
 

        

        
~
    MyBase.SetItem(index, item)
Class AnimalCollection
+
  End Sub
 

        

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

        

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

        

        
~
Module Sample
Class GenericTypeInheritanceSample
+
  Public Sub Main()
+
    Dim col As New PositiveNumberCollection()
 

        

        
~
    col.Add(0)
    Shared Sub Main()
+
    col.Add(1)
 

        

        
~
    ' ArgumentOutOfRangeExceptionがスローされる
        Dim animals As New AnimalCollection
~
    col.Add(-1)

          
~
  End Sub
        animals.Add(New Cat())
~
End Module
        animals.Add(New Dog())
~
}}
        animals.Add(New Cat())
-
        animals.Add(New Cat())
-
        animals.Add(New Dog())
-

          
-
        For Each animal as Animal In animals
-

          
-
          animal.Say()
 

        

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

        

        
~
詳しくは制約については[[programming/vb.net/diff_7.xto8/06_constraints]]で解説しています。
    End Sub
 

        

        
-
End Class
-
}}
 

        

        
-
#prompt(実行結果){{
-
meow
-
bow wow
-
meow
-
meow
-
bow wow
-
}}
 

        

        
-
#navi(..)