2013-10-09T22:51:12の更新内容

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

current previous
1,319 1,179
 
${smdncms:title,制約}
${smdncms:title,制約}
~
${smdncms:keywords,ジェネリクス,ジェネリック,型パラメータ,制約}
${smdncms:keywords,制約}
 
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,visual basic}
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,visual basic}
~

          
#navi(..)
~
[[ジェネリック型やジェネリックメソッド>programming/vb.net/generics]]では``Of句``を使うことであらゆる型をパラメータとして使用できるようになります。 しかし、型パラメータに指定できる型をある程度限定したい場合もあります。 例えば、型パラメータに指定できる型をなんらかのインターフェイスを実装するクラスや、値型・参照型のみにしたい場合などです。 こういった場合には、``Of``句に続けて``As``句を加えることで型パラメータに''制約''を設けることができます。
ジェネリックな型では、Ofを使うことであらゆる型をパラメータとして使用できるようになります。 しかし、コレクションクラスなどでは、ある型とその型を継承した型だけしか扱いたくないなどの理由で、逆に型を限定したくなる場合もあります。 そのような時には、「Of ~ As …」のようにAs句を加えることで指定できる型を限定することが出来ます。
 

        

        
 
-関連するページ
-関連するページ
+
--[[programming/vb.net/generics]]
 
--[[programming/netfx/valuetype_referencetype]]
--[[programming/netfx/valuetype_referencetype]]
 

        

        
 
#googleadunit
#googleadunit
 

        

        
~
*制約の例
*型の制約
~
制約の例として、オブジェクトを複製するジェネリックメソッドを作成する例を考えてみます。 ここでは、次のようなメソッドを作成することにします。
この例では、Program.Clone()メソッドはIConeableを実装するクラスを引数に要求するよう型の制約を指定しています。
 

        

        
 
#code(vb){{
#code(vb){{
~
' TObject型の引数objを複製するメソッド
Class CloneableType
~
Function CloneObject(Of TObject)(ByVal obj As TObject) As TObject

          
~
  ' objを複製したものを返したい
    Implements ICloneable
~
  Return obj

          
~
End Function
    Public Function Clone() As Object Implements System.ICloneable.Clone
+
}}
 

        

        
~
CloneObjectメソッドは、引数で与えられたオブジェクトを複製して返すものとします。 このメソッドでは引数objの型および戻り値の型を決定するために型パラメータTObjectを使用します。
        Return MyBase.MemberwiseClone()
 

        

        
~
上記の動作を満たすようにメソッドの内容を書き換えていきます。 まず、引数objを複製するためにCloneObjectメソッドを使うように書き換えます。 同時に、戻り値の型をTObjectにするため[[DirectCast演算子>programming/vb.net/operator_directcast_trycast#DirectCastOperator]]でキャストするようにします。
    End Function
 

        

        
~
#code(vb){{
End Class
+
' TObject型の引数objを複製するメソッド
+
Function CloneObject(Of TObject)(ByVal obj As TObject) As TObject
+
  ' Cloneメソッドを使って複製を作成し、TObjectにキャストして返すようにしたい
+
  Return DirectCast(obj.Clone(), TObject)
+
  ' BC30456: 'Clone' は 'TObject' のメンバではありません。
+
End Function
+
}}
 

        

        
~
この段階では上記のようにコンパイルエラーが発生します。 objの型はTObjectですが、TObjectにはCloneメソッドが存在しないためこのようなエラーとなります。 これは呼び出し側でTObjectの具体的な型が指定されるまでTObjectがCloneメソッドを持つかどうかわからないためです。
Class NonCloneableType : End Class
 

        

        
~
このエラーを回避するため、型パラメータTObjectに''制約''を指定することにします。 型TObjectがCloneメソッドを持つ型に限定されるようにするため、「TObjectは[[ICloneableインターフェイス>programming/netfx/cloning#ICloneable]]を実装していなければならない」という制約を設けます。 具体的には、メソッド宣言部の``Of``句に続けて``As ICloneable``を追加します。
Class TypeConstraintsSample
 

        

        
~
#code(vb){{
    Shared Sub Main()
+
' ICloneableインターフェイスを実装するTObject型の引数objを複製するメソッド
+
Function CloneObject(Of TObject As ICloneable)(ByVal obj As TObject) As TObject
+
  ' Cloneメソッドを使って複製を作成し、TObjectにキャストして返す
+
  Return DirectCast(obj.Clone(), TObject)
+
End Function
+
}}
 

        

        
~
このように制約を設けることで、ジェネリックメソッド内では型パラメータが実装するICloneableインターフェイスのメンバ(=Cloneメソッド)を呼び出すことができるようになり、同時に型パラメータに指定できる型をICloneableインターフェイスを実装する型に限定することができるようになります。 一方、制約に反する型を指定してこのメソッドを呼び出そうとした場合にはコンパイルエラーとなります。
        Dim cloneable As New CloneableType
-
        Dim nonCloneable As New NonCloneableType
 

        

        
~
#code(vb){{
        Clone(Of CloneableType)(cloneable)
+
Imports System
 

        

        
~
Module Sample
        ' error BC32044: 型引数 'NonCloneableType' は、制約型 'System.ICloneable' から継承したり、この型を実装したりしません。
~
  Function CloneObject(Of TObject As ICloneable)(ByVal obj As TObject) As TObject
        Clone(Of NonCloneableType)(nonCloneable)
+
    ' Cloneメソッドを使って複製を作成し、TObjectにキャストして返す
+
    Return DirectCast(obj.Clone(), TObject)
+
  End Function
+

          
+
  Public Sub Main()
+
    Dim s1 As String = "foo"
+
    Dim s2 As String = CloneObject(s1) ' StringはICloneableを実装しているため、CloneObjectメソッドを呼び出せる
+

          
+
    Dim u1() As Integer = New Integer() {1, 2, 3}
+
    Dim u2() As Integer = CloneObject(u1) ' 配列(Array)もICloneableを実装しているため、CloneObjectメソッドを呼び出せる
+

          
+
    Dim i1 As Integer = 1
+
    Dim i2 As Integer = CloneObject(i1) ' IntegerはICloneableを実装していないため、CloneObjectメソッドを呼び出せない
+
    ' BC32044: 型引数 'Integer' は、制約型 'System.ICloneable' から継承したり、この型を実装したりしません。
+
  End Sub
+
End Module
+
}}
 

        

        
-
    End Sub
 

        

        
~
*インターフェイスの制約
    Shared Function Clone(Of T As ICloneable)(ByVal c As T) As T
+
先の例のように、制約には通常のインターフェイスを指定できるほか、ジェネリックなインターフェイスを指定することもできます。 例えば、[[IComparable(Of T)インターフェイス>programming/netfx/comparison/0_comparison#IComparableOfT]]を実装する二つの引数のうち''大きい方''を返すMaxメソッドを作成すると次のようになります。 このメソッドでは、型パラメータTに「IComparable(Of T)インターフェイスを実装していること」を制約として求めています。
 

        

        
~
#code(vb){{
        Return DirectCast(c.Clone(), T)
+
Imports System
 

        

        
~
Module Sample
    End Function
+
  ' 引数で与えられた二つの値のうち、大き方を返すジェネリックメソッド
+
  Function Max(Of T As IComparable(Of T))(ByVal x As T, ByVal y As T) As T
+
    ' IComparable(Of T).CompareToメソッドを使ってどちらが大きい値を持つか判別する
+
    If 0 <= x.CompareTo(y) Then
+
      Return x
+
    Else
+
      Return y
+
    End If
+
  End Function
+

          
+
  Public Sub Main()
+
    Console.WriteLine(Max(3, 16))
+

          
+
    Console.WriteLine(Max(3.1416, 2.7183))
+

          
+
    Console.WriteLine(Max("bar", "foo"))
+
  End Sub
+
End Module
+
}}
 

        

        
~
#prompt(実行結果){{
End Class
+
16
+
3.1416
+
foo
 
}}
}}
 

        

        
-
エラーメッセージを見て分かるとおり、Clone()メソッドにICloneableを実装していないクラスを渡そうとしてもエラーになるため呼び出せなくなります。 この例において、Clone()メソッドはジェネリック型の制約の説明のためあえてジェネリックを用いていますが、引数の型を単にICloneableすることでも同様のことはできます。
 

        

        
~
*クラスの制約
*値型・参照型の制約
~
制約にはインターフェイスだけでなく次の例のように基底クラスを指定することもできます。
型の制約に加え、パラメータとする型が値型か参照型かを制約することが出来ます。 値型であることを要求する場合にはAs Structure、参照型であることを要求する場合にはAs Classを指定します。
 

        

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

        

        
~
' 基底クラス
''' <summary>
~
Class Control
''' 型パラメータに値型を取るクラス
~
  ' Clickイベント
''' </summary>
~
  Public Event Click As EventHandler
Class ValueTypedClass(Of T As Structure) : End Class
+
End Class
 

        

        
~
' Controlを継承したButtonクラス
''' <summary>
~
Class Button
''' 型パラメータに参照型を取るクラス
~
  Inherits Control
''' </summary>
~
End Class
Class ReferenceTypedClass(Of T As Class) : End Class
 

        

        
~
' Controlを継承したCheckBoxクラス
Class TypeConstraintsSample
+
Class CheckBox
+
  Inherits Control
+
End Class
 

        

        
~
Module Sample
    Shared Sub Main()
+
  ' Clickイベントのハンドラ
+
  Sub OnClick(ByVal sender As Object, ByVal e As EventArgs)
+
    ' 実装は省略
+
  End Sub
+

          
+
  ' ControlにClickイベントのハンドラを割り当てるジェネリックメソッド
+
  Sub AddClickEvent(Of TControl As Control)(ByVal c As TControl)
+
    AddHandler c.Click, AddressOf OnClick
+
  End Sub
+

          
+
  Public Sub Main()
+
    Dim b As New Button()
+
    Dim c As New CheckBox()
+

          
+
    AddClickEvent(b)
+
    AddClickEvent(c)
+
  End Sub
+
End Module
+
}}
+

          
+
ただ、この例のような場合、引数の型を単に基底クラスの型にすればよいので制約を用いる必要性はありません。
+

          
+
#code(vb){{
+
' ControlにClickイベントのハンドラを割り当てる(非ジェネリックな)メソッド
+
Sub AddClickEvent(ByVal c As Control)
+
  AddHandler c.Click, AddressOf OnClick
+
End Sub
+
}}
 

        

        
~
このように、ジェネリクスを使わなくてもポリモーフィズムによって実現できる場合と、ジェネリクスと制約を使わなければ実現できない場合があるため、両者を適切に使い分ける必要があります。
        ' OK
-
        Dim v1 As ValueTypedClass(Of Integer) = new ValueTypedClass(Of Integer)
 

        

        
-
        ' error BC32105: 型引数 'System.Random' は型パラメータ 'T' の 'Structure' 制約を満たしていません。
-
        Dim v2 As ValueTypedClass(Of Random)   = new ValueTypedClass(Of Random)
 

        

        
~
*値型・参照型の制約
        ' error BC32106: 型引数 'Integer' は型パラメータ 'T' の 'Class' 制約を満たしていません。
~
インターフェイスやクラスなどの具体的な型による制約だけでなく、値型または参照型のどちらかに限定する制約を指定することもできます。 型パラメータが値型であることを要求する場合は``Of``句に続けて``As Structure``、参照型であることを要求する場合は続けて``As Class``を指定します。
        Dim r1 As ReferenceTypedClass(Of Integer) = new ReferenceTypedClass(Of Integer)
 

        

        
~
#code(vb){{
        ' OK
~
Imports System
        Dim r2 As ReferenceTypedClass(Of Random)   = new ReferenceTypedClass(Of Random)
+
Imports System.Collections.ObjectModel
 

        

        
~
' 参照型の型パラメータTRefを要求するコレクション
    End Sub
+
Class ReferenceTypeCollection(Of TRef As Class)
+
  Inherits Collection(Of TRef)
+
End Class
 

        

        
+
' 値型の型パラメータTValを要求するコレクション
+
Class ValueTypeCollection(Of TVal As Structure)
+
  Inherits Collection(Of TVal)
 
End Class
End Class
 
}}
}}
 

        

        
~
値型・参照型と制約については[[programming/netfx/valuetype_referencetype#generic_type_constraints]]でも解説しています。
このように制約を満たしていない型パラメータを指定しようとすると、コンパイルエラーとなります。
 

        

        
~
**デフォルト値
*コンストラクタの制約
~
ジェネリックメソッド・ジェネリック型で総称型(=パラメータ化された型)の値を初期化する場合、値型か参照型かによって場合分けする必要はなく、ともにNothingを代入することで初期化できます。 例えば次のような配列の内容をクリアするジェネリックメソッドを考えます。
ジェネリクスなクラス、メソッドを作る際に、パラメータ化された型が必ずコンストラクタを持つように要求することが出来ます。 次の例のように、Ofキーワードで型を指定するのと同じ要領で「As New」を指定することで、引数を取らないコンストラクタを持つ型(つまりデフォルトコンストラクタを持つ型)だけを扱えるようにすることが出来ます。
 

        

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

        

        
~
Module Sample
''' <summary>
~
  ' 参照型配列の内容をクリアするメソッド
''' デフォルトコンストラクタを持つ型を内包するクラス
~
  Sub ClearRefArray(Of TRef As Class)(ByVal arr() As TRef)
''' </summary>
~
    For i As Integer = 0 To arr.Length - 1
Class WithDefaultConstructor(Of T As New)
~
      ' 配列内の各要素にNothingを指定する

          
~
      arr(i) = Nothing
    Public Val As T
~
    Next

          
~
  End Sub
    Public Sub New()
~

          

          
~
  ' 値型配列の内容をクリアするメソッド
        Val = New T()
~
  Sub ClearValArray(Of TVal As Structure)(ByVal arr() As TVal)

          
~
    For i As Integer = 0 To arr.Length - 1
    End Sub
+
      ' 配列内の各要素にデフォルトコンストラクタで初期化した値を指定する
+
      arr(i) = New TVal()
+
    Next
+
  End Sub
+

          
+
  Public Sub Main()
+
    Dim refarr() As String = New String() {"foo", "bar", "baz"}
+

          
+
    ClearRefArray(refarr)
+

          
+
    Dim valarr() As Integer = New Integer() {1, 2, 3}
+

          
+
    ClearValArray(valarr)
+
  End Sub
+
End Module
+
}}
 

        

        
~
上記の例では、参照型用と値型用の二つのジェネリックメソッドを用意しています。 この例の場合、制約によって値型・参照型に限定するまでもなくどちらもNothingの代入によりデフォルト値への初期化を行うことができます。
    Public Overrides Function ToString() As String
 

        

        
~
#code(vb){{
        Return Val.ToString()
+
Imports System
 

        

        
~
Module Sample
    End Function
+
  ' 配列の内容をクリアするメソッド
+
  Sub ClearArray(Of T)(ByVal arr() As T)
+
    For i As Integer = 0 To arr.Length - 1
+
      ' Nothingを指定してデフォルト値に初期化する
+
      arr(i) = Nothing
+
    Next
+
  End Sub
+

          
+
  Public Sub Main()
+
    Dim refarr() As String = New String() {"foo", "bar", "baz"}
+

          
+
    ClearArray(refarr)
+

          
+
    Dim valarr() As Integer = New Integer() {1, 2, 3}
+

          
+
    ClearArray(valarr)
+
  End Sub
+
End Module
+
}}
 

        

        
~
値型・参照型とデフォルト値については[[programming/netfx/basic_types/0_characteristics#DefaultValue]]、値型(構造体)の初期化については[[programming/vb.net/basics/09_structure]]でも解説しています。
End Class
 

        

        
-
''' <summary>
-
''' デフォルトコンストラクタを持たない型を内包するクラス
-
''' </summary>
-
Class WithNoDefaultConstructor(Of T)
 

        

        
~
*コンストラクタの制約
    Public Val As T
+
制約により、パラメータ化された型が必ずコンストラクタを持つように要求することもできます。 ``Of``句に続けて``As New``と指定することにより、型パラメータにコンストラクタを持つ型のみを指定できるように限定することができます。 ただし、この制約により指定できるのは引数を取らないコンストラクタ(''デフォルトコンストラクタ'')のみとなります。 引数を取るコンストラクタを制約で指定することはできません。
 

        

        
~
#code(vb){{
    Public Overrides Function ToString() As String
+
Imports System
 

        

        
~
Module Sample
        Return Val.ToString()
+
  ' 引数のないコンストラクタを持つ型Tを要求するメソッド
+
  Sub FillArray(Of T As New)(ByVal arr() As T)
+
    For i As Integer = 0 To arr.Length - 1
+
      arr(i) = New T()
+
    Next
+
  End Sub
 

        

        
~
  Public Sub Main()
    End Function
+
    Dim arr1(2) As UriBuilder
 

        

        
~
    FillArray(arr1) ' UriBuilderは引数のないコンストラクタを持つため、呼び出せる
End Class
 

        

        
~
    Dim arr2(4) As Integer
Class ConstructorConstraintsSample
 

        

        
~
    FillArray(arr2) ' Integer(構造体)も引数のないコンストラクタを持つため、呼び出せる
    Shared Sub Main()
 

        

        
~
    Dim arr3(9) As String
        Dim a1 As New WithDefaultConstructor(Of Integer)
-
        Dim b1 As New WithNoDefaultConstructor(Of Integer)
 

        

        
~
    FillArray(arr3) ' Stringには引数のないコンストラクタはないため、呼び出せない
        Console.WriteLine(a1.ToString())
~
  End Sub
        Console.WriteLine(b1.ToString())
+
End Module
+
}}
 

        

        
~
このように、制約で``As New``を指定した場合はデフォルトコンストラクタを持つクラスまたは暗黙的にデフォルトコンストラクタが用意される構造体のみが型パラメータとして指定できるようになります。
        Dim a2 As New WithDefaultConstructor(Of Random)
-
        Dim b2 As New WithNoDefaultConstructor(Of Random)
 

        

        
-
        Console.WriteLine(a2.ToString())
-
        Console.WriteLine(b2.ToString())
 

        

        
~
*制約の組み合わせ
    End Sub
+
ここまでで紹介してきた制約を複数組み合わせて指定することもできます。 複数の制約を指定する場合は、``Of T As {制約1, 制約2, ...}``のように中括弧でくくって指定します。 例えば次の制約の場合、「型パラメータTは参照型(``Class``)で、デフォルトコンストラクタを持ち(``New``)、かつ[[IDisposableインターフェイス>programming/netfx/disposing#IDisposable]]を実装している」ことを要求します。
 

        

        
+
#code(vb){{
+
Class GenericClass(Of T As {Class, New, IDisposable})
 
End Class
End Class
 
}}
}}
 

        

        
-
#prompt(実行結果){{
-
0
-
0
-
System.Random
 

        

        
~
*制約と列挙体
ハンドルされていない例外: System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。
~
値型(構造体)・参照型(インターフェイス・クラス)に対応する制約は存在しますが、[[列挙体(Enum)>programming/netfx/enum]]に対応する制約は存在しません。 そのため、型パラメータに指定できる型を列挙型に限定したい場合は、列挙体の基底型である構造体(Structure)を制約として指定します。 当然これだけでは制約としては不十分なので、必要に応じてメソッド内で型情報を調べて実際に指定された型が列挙体かどうかを調べるようにすることもできます。
   場所 WithNoDefaultConstructor`1.ToString()
-
   場所 Program.Main()
-
}}
 

        

        
~
#code(vb){{
この例のように、ジェネリック型でパラメータに渡された型Tのインスタンスを生成したいなど、Tがデフォルトコンストラクタを持っていることを要求したい場合は、OfキーワードとともにAs Newを指定することができます。
+
Imports System
 

        

        
~
Module Sample
ただし、コンストラクタの制約はデフォルトコンストラクタを持つかどうかだけを指定できますが、パラメータのあるコンストラクタを持つかどうかは制約として指定できません。 そういった場合はパラメータのあるコンストラクタを持つ型を型の制約で指定します。
+
  ' 文字列から列挙体の値に変換するメソッド
+
  Function Parse(Of TEnum As Structure)(ByVal str As String) As TEnum
+
    ' 型パラメータTEnumの型情報を取得する
+
    Dim t As Type = GetType(TEnum)
+

          
+
    ' 型パラメータTEnumに指定された型が列挙体かどうか調べる
+
    If Not t.IsEnum Then Throw New ArgumentException("列挙体ではありません")
+

          
+
    ' 列挙体の場合は、Enum.Parseメソッドを使って文字列から列挙体に変換する
+
    Return CType([Enum].Parse(t, str, True), TEnum)
+
  End Function
+

          
+
  Public Sub Main()
+
    ' 文字列からDayOfWeek列挙体に変換する
+
    Dim d As DayOfWeek = Parse(Of DayOfWeek)("friday")
+

          
+
    Console.WriteLine(d)
+
  End Sub
+
End Module
+
}}
 

        

        
-
*複数の制約
-
ここまでで説明してきた制約を組み合わせることも出来ます。 複数の制約を指定する場合は、制約{ }で括ります。 次の例では、型Tは参照型でデフォルトコンストラクタを持ち、なおかつIDisposableを実装することを要求します。
 

        

        
-
#code(vb){{
-
Class SomeObjectClass(Of T As {Class, New, IDisposable})
-
End Class
-
}}
 

        

        
-
#navi(..)