2014-08-22T22:27:16の更新内容

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

current previous
1,455 0,0
+
${smdncms:title,演算子のオーバーロード}
+
${smdncms:keywords,演算子,オーバーロード,演算子プロシージャ}
+
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,visual basic}
+

          
+
VB8(VB2005)以降では''演算子のオーバーロード''を行うことができます。 演算子のオーバーロードとは、端的に言えばクラスや構造体に対して``+``や``*``などの演算子による演算を行えるようにすることです。 演算子をオーバーロードすることにより、演算子を使ってクラスや構造体同士を演算できるようにすることができます。 例えば、独自に定義した数値型を作成したとして、これと演算子を使って加算や乗算などの演算を行えるようにするには``+``や``*``といった演算子をオーバーロードします。
+

          
+
演算子のオーバーロードといってもそれほど特殊な記述をする必要はなく、通常の[[プロシージャ>programming/vb.net/procedure]]を定義するのと同様に演算子に対応する''演算子プロシージャ''を用意するだけで、これによって演算子を使った演算が出来るようになります。
+

          
+
-関連するページ
+
--[[programming/vb.net/procedure]]
+
--[[programming/vb.net/basic_type_conversion]]
+
--[[programming/netfx/conversion/1_userdefined]]
+
--[[programming/netfx/comparison/0_relational_operator_overload]]
+
--[[programming/netfx/comparison/1_equivalence_operator_overload]]
+

          
+
#googleadunit
+

          
+
*演算子のオーバーロード
+
例えば次のような構造体Vector型を作成し、このVector型の値同士を``+``演算子で加算できるようにしたい場合を考えます。
+

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

          
+
Structure Vector
+
  Public X As Double
+
  Public Y As Double
+

          
+
  Public Sub New(ByVal x As Double, ByVal y As Double)
+
    Me.X = x
+
    Me.Y = y
+
  End Sub
+

          
+
  Public Overrides Function ToString() As String
+
    Return String.Format("({0}, {1})", X, Y)
+
  End Function
+
End Structure
+

          
+
Module Sample
+
  Sub Main()
+
    Dim a As New Vector(1.0, 0.0)
+
    Dim b As New Vector(3.0, 4.0)
+

          
+
    ' Vector型同士を演算する+演算子のオーバーロードが存在しないのでエラーとなる
+
    ' error BC30452: 演算子 '+' は、型 'Vector' および 'Vector' に対して定義されていません。
+
    Dim c As Vector = a + b
+
  End Sub
+
End Module
+
}}
+

          
+
このままでは、``a + b``の部分、つまりVector構造体同士に対して``+``演算子を使った場合の動作が定義されていないため、上記のようにエラーとなります。
+

          
+
そこで``+``演算子による演算の動作を定義するために``+``演算子をオーバーロードします。 具体的には、Vector構造体に次のようなプロシージャを記述します。
+

          
+
#code(vb,+演算子の演算内容を定義する演算子プロシージャ){{
+
' +演算子のオーバーロード
+
' 引数leftには演算子左項の値、rightには右項の値が引き渡される
+
Public Shared Operator +(ByVal left As Vector, ByVal right As Vector) As Vector
+
  ' 右項と左項それぞれのX, Yの値を足したものを結果として返す
+
  Return New Vector(left.X + right.X, left.Y + right.Y)
+
End Operator
+
}}
+

          
+
演算子のオーバーロードを行うプロシージャは、''演算子プロシージャ''と呼ばれます。 演算子プロシージャが[[通常のプロシージャ>programming/vb.net/procedure]]と異なる点は、''宣言に``Function``ではなく``Operator``を使う''点、また''プロシージャ名の代わりにオーバーロードしたい演算子を記述する''点です。
+

          
+
``+``演算子をオーバーロードする場合は、演算子の右項と左項にあたる値を引数として受けるようにする必要があります。 ここでは引数&var{left};が左項、&var{right};が右項になります。 また、戻り値の型も演算結果に応じたものにする必要があります。 ここでは二つのVector構造体の値を加算しその結果をVector構造体として返したいので、引数・戻り値ともにVector構造体になります。
+

          
+
このようにして``+``演算子をオーバーロードすると、先のコードが実行できるようになります。
+

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

          
+
Structure Vector
+
  Public X As Double
+
  Public Y As Double
+

          
+
  Public Sub New(ByVal x As Double, ByVal y As Double)
+
    Me.X = x
+
    Me.Y = y
+
  End Sub
+

          
+
  ' +演算子のオーバーロード
+
  ' 引数leftには演算子左項の値、rightには右項の値が引き渡される
+
  Public Shared Operator +(ByVal left As Vector, ByVal right As Vector) As Vector
+
    ' 右項と左項それぞれのX, Yの値を足したものを結果として返す
+
    Return New Vector(left.X + right.X, left.Y + right.Y)
+
  End Operator
+

          
+
  Public Overrides Function ToString() As String
+
    Return String.Format("({0}, {1})", X, Y)
+
  End Function
+
End Structure
+

          
+
Module Sample
+
  Sub Main()
+
    Dim a As New Vector(1.0, 0.0)
+
    Dim b As New Vector(3.0, 4.0)
+

          
+
    Dim c As Vector = a + b ' オーバーロードされた+演算子が呼び出され、結果がcに代入される
+

          
+
    Console.WriteLine("{0} + {1} = {2}", a, b, c)
+
  End Sub
+
End Module
+
}}
+

          
+
#prompt(実行結果){{
+
(1, 0) + (3, 4) = (4, 4)
+
}}
+

          
+
ここまでは構造体を使って演算子をオーバーロードする例を紹介しましたが、クラスの場合でも同様に演算子プロシージャを記述することにより演算子をオーバーロードすることができます。
+

          
+
通常のプロシージャと同様に、引数の型が異なっていれば演算子プロシージャも複数のバージョンを用意することができます。 そのため、一つの演算子に対して複数の演算子プロシージャを用意することができ、演算内容を様々に定義することができます。
+

          
+
例えば次のように、``*``演算子に対してベクトルの定数倍と内積の二つを定義することもできます。
+

          
+
#code(vb,*演算子をオーバーロードしてベクトルの定数倍と内積の演算を定義する例){{
+
Imports System
+

          
+
Structure Vector
+
  Public X As Double
+
  Public Y As Double
+

          
+
  Public Sub New(ByVal x As Double, ByVal y As Double)
+
    Me.X = x
+
    Me.Y = y
+
  End Sub
+

          
+
  ' *演算子のオーバーロード (左項がDouble型、右項がVector型、戻り値はVector型)
+
  Public Shared Operator *(ByVal left As Double, ByVal right As Vector) As Vector
+
    ' 左項の値で右項のベクトルを定数倍した値を返す
+
    Return New Vector(left * right.X, left * right.Y)
+
  End Operator
+

          
+
  ' *演算子のオーバーロード (左項・右項ともにVector型、戻り値はDouble型)
+
  Public Shared Operator *(ByVal left As Vector, ByVal right As Vector) As Double
+
    ' 左項と右項のベクトルの内積を返す
+
    Return left.X * right.X + left.Y * right.Y
+
  End Operator
+

          
+
  Public Overrides Function ToString() As String
+
    Return String.Format("({0}, {1})", X, Y)
+
  End Function
+
End Structure
+

          
+
Module Sample
+
  Sub Main()
+
    Dim a As New Vector(2.0, 1.0)
+
    Dim b As New Vector(3.0, 0.0)
+

          
+
    ' aを定数倍した値を求める
+
    Console.WriteLine("{0} * {1} = {2}", 2.0, a, 2.0 * a)
+

          
+
    ' aとbの内積を求める
+
    Console.WriteLine("{0} * {1} = {2}", a, b, a * b)
+
  End Sub
+
End Module
+
}}
+

          
+
#prompt(実行結果){{
+
2 * (2, 1) = (4, 2)
+
(2, 1) * (3, 0) = 6
+
}}
+

          
+
この例からも明らかなように、演算子プロシージャの引数(演算子の項)および戻り値(演算の結果)はそれぞれ異なる型となっていてもよく、演算が定義できるのであれば引数と戻り値には任意の型を選択することができます。
+

          
+

          
+
*オーバーロードすることができる演算子
+
VB.NETでは、以下の演算子をオーバーロードすることができます。
+

          
+
|>|~演算子の種類|~オーバーロードできる演算子|h
+
|~算術演算子|~単項|``+`` ``-``|
+
|~|~二項|``+`` ``-`` ``*`` ``/`` ``\`` ``Mod`` ``^``|
+
|~ビット演算子|~単項|``Not``|
+
|~|~二項| ``<<`` ``>>`` ``And`` ``Or`` ``Xor``|
+
|~論理演算子|~単項|``IsTrue`` ``IsFalse``|
+
|~比較演算子&br;(関係演算子)|~二項|``=`` ``<>`` ``<`` ``>`` ``<=`` ``>=`` ``Like``
+
|~型変換演算子|~単項|``CType``|
+

          
+
単項の``+``および``-``は、符号を持つ値&var{x};に対して符号をそのままにしたもの(``+x``)、符号を反転したもの(``-x``)を求める演算子です。
+

          
+

          
+
**対になっている演算子のオーバーロード
+
つぎの演算子は二つの演算子を両方オーバーロードする必要があります。
+

          
+
-等価・不等価演算子``=``と``<>``
+
-関係演算子``<``と``>``および``<=``と``>=``
+
-論理演算子``IsTrue``と``IsFalse``
+

          
+
これらの演算子は対になっているため、どちらか片方だけをオーバーロードすることはできません。
+

          
+

          
+
*比較演算子のオーバーロードとインターフェイスの実装
+
``<``や``>``などの比較演算子に似た役割を持つインターフェイスとして、.NET Frameworkには[[IComparable, IComparerインターフェイス>programming/netfx/comparison/0_comparison]]が存在します。 比較演算子とインターフェイスを同時に実装する場合の方法や注意点については[[programming/netfx/comparison/0_relational_operator_overload]]で解説しています。
+

          
+
同様に、``=``と``<>``の等価・不等価演算子と[[Object.Equalsメソッド・IEquatableインターフェイス>programming/netfx/comparison/1_equation]]については[[programming/netfx/comparison/1_equivalence_operator_overload]]で解説しています。
+

          
+

          
+
*論理演算子のオーバーロード
+
**IsTrue演算子・IsFalse演算子
+
``IsTrue``演算子・``IsFalse``演算子は、値が真(``True``)または偽(``False``)として評価できるかどうかを調べる演算子です。 ``+``や``And``などの演算子とは異なりこの演算子を直接使用することはできませんが、この演算子をオーバーロードすると条件式の中で直接値の真偽値(値が``True``か``False``か)を評価することができるようになります。 つまり、``If x Then ...``のように条件式を記述することができるようになります。
+

          
+
一方、``And``などの演算子を条件式内で論理演算子として使用する場合にも``IsTrue``・``IsFalse``演算子をオーバーロードする必要があります。 これについて詳しくは次の節で解説します。
+

          
+

          
+
**論理演算子のオーバーロードおよび条件式での使用
+
``Not``、``And``、``Or``などの演算子を``If``文などの条件式内において論理演算子として使用するには、``IsTrue``・``IsFalse``演算子もオーバーロードする必要があります。 例えば``And``演算子をオーバーロードするだけでは``If x And y Then ...``のような条件式を評価できるようにはならず、こういった条件式はコンパイルエラーとなります。
+

          
+
また``AndAlso``・``OrElse``演算子は演算子プロシージャによって直接オーバーロードすることはできませんが、``AndAlso``演算子は``And``演算子および``IsTrue``・``IsFalse``演算子をオーバーロードすることにより使用できるようになります。 同様に``OrElse``演算子も``Or``演算子および``IsTrue``・``IsFalse``演算子をオーバーロードすることにより使用できるようになります。
+

          
+
以下は``And``・``IsTrue``・``IsFalse``演算子をオーバーロードして条件式で``And``・``AndAlso``演算子を使用できるようにする例です。
+

          
+
#code(vb,And・IsTrue・IsFalseの各演算子をオーバーロードする例){{
+
Imports System
+

          
+
' 0をFalse、それ以外の値をTrueとして扱うブーリアン型
+
Structure Bool
+
  Public Value As Integer
+

          
+
  Public Sub New(ByVal value As Integer)
+
    Me.Value = value
+
  End Sub
+

          
+
  ' And演算子のオーバーロード
+
  Public Shared Operator And(ByVal left As Bool, ByVal right As Bool) As Bool
+
    ' 左項と右項の値に対してビットごとのAndをとり、その結果を返す
+
    Return New Bool(left.Value And right.Value)
+
  End Operator
+

          
+
  ' IsTrue演算子のオーバーロード
+
  Public Shared Operator IsTrue(ByVal b As Bool) As Boolean
+
    ' 値が0以外であればTrueと評価する
+
    Return b.Value <> 0
+
  End Operator
+

          
+
  ' IsFalse演算子のオーバーロード
+
  Public Shared Operator IsFalse(ByVal b As Bool) As Boolean
+
    ' 値が0であればFalseと評価する
+
    Return b.Value = 0
+
  End Operator
+
End Structure
+

          
+
Module Sample
+
  Sub Main()
+
    Dim x As New Bool(0) ' False
+
    Dim y As New Bool(1) ' True
+

          
+
    Console.Write("x And y = ")
+

          
+
    ' And演算子で二つの値の論理積を求める
+
    If x And y Then
+
      Console.WriteLine("True")
+
    Else
+
      Console.WriteLine("False")
+
    End If
+

          
+
    Console.Write("x AndAlso y = ")
+

          
+
    ' AndAlso演算子で二つの値の論理積を求める
+
    If x AndAlso y Then
+
      Console.WriteLine("True")
+
    Else
+
      Console.WriteLine("False")
+
    End If
+

          
+
    Console.Write("x = ")
+

          
+
    ' 値の真偽を評価する (IsTrue演算子が使用される)
+
    If x Then
+
      Console.WriteLine("True")
+
    Else
+
      Console.WriteLine("False")
+
    End If
+
  End Sub
+
End Module
+
}}
+

          
+
#prompt(実行結果){{
+
x And y = False
+
x AndAlso y = False
+
x = False
+
}}
+

          
+
上記の例において、それぞれの条件式は次のように評価されます。 ``And``演算子と``AndAlso``演算子で演算子プロシージャが呼び出される順序が異なる点に注意してください。
+

          
+
|~条件式|~条件式の評価順序|h
+
|~``x And y``|``And``演算子により``x``と``y``のビット毎の積(And)が求められ、次に``IsTrue``演算子によってその値が``True``かどうかが評価される。|
+
|~``x AndAlso y``|``IsTrue``演算子によって``x``が``True``かどうかが評価され、``True``だった場合は続けて``y``が真かどうかが評価される。&br;``x``が``False``だった場合はそれが条件式の値となり、``y``の値は評価されない。(ショートサーキット)|
+
|~``x``|``IsTrue``演算子によって``x``が``True``かどうかが評価される。|
+

          
+

          
+
*型変換演算子のオーバーロード
+
``CType``演算子をオーバーロードすることで構造体やクラスに他の型への変換処理を実装することができます。 基本的には他の演算子と同様に演算子プロシージャを記述することでオーバーロードすることができますが、``CType``の演算子プロシージャは常に``Widening``または``Narrowing``キーワードを伴います。 つまり、``CType``の演算子プロシージャは以下の形式となります。
+

          
+
#code(vb,CType演算子プロシージャの形式){{
+
Public Shared Widening Operator CType(ByVal val As 型) As 変換後の型
+
  Return 型変換後の値
+
End Operator
+

          
+
Public Shared Narrowing Operator CType(ByVal val As 型) As 変換後の型
+
  Return 型変換後の値
+
End Operator
+
}}
+

          
+
``Widening``キーワードは型変換が''拡大変換''であることを表します。 これは``Integer``→``Long``や``Long``→``Double``のように、データの欠損や桁落ち、オーバーフローが発生しない型変換のことです。 このような''失敗の可能性がない型変換''を定義する場合には演算子プロシージャに``Widening``キーワードを付けます。 ``Widening``キーワードが指定されている型変換では''暗黙の型変換''を行うことが許可されます。
+

          
+
逆に``Narrowing``キーワードは型変換が''縮小変換''であることを表します。 これは``Long``→``Integer``や``Double``→``Long``のように、データの欠損や桁落ち、オーバーフローが発生する可能性のある型変換のことです。 このような''失敗する可能性がある型変換''を定義する場合には演算子プロシージャに``Narrowing``キーワードを付けます。 ``Narrowing``キーワードが指定されている型変換では常に''明示的な型変換''を行うことが強制されます。
+

          
+
(拡大変換・縮小変換および明示的・暗黙的な型変換に関しては[[programming/netfx/conversion/0_basetype]]でも解説しています)
+

          
+
CType演算子プロシージャはサポートしたい型変換の種類に応じて複数記述することができます。
+

          
+
以下はCType演算子をオーバーロードする例です。 この例では24ビットの符号付き整数型``Int24``を作成し、``Integer``(32ビット)への拡大変換と``Short``(16ビット)への縮小変換の二種類の型変換を定義しています。
+

          
+
#code(vb,CType演算子をオーバーロードする例){{
+
Option Strict On
+

          
+
Imports System
+

          
+
' 24ビットの符号付き整数型
+
Structure Int24
+
  Public Const MaxValue As Integer = 8388607 ' &H7FFFFF
+
  Public Const MinValue As Integer = -8388608 ' &H800000
+

          
+
  Public Value As Integer
+

          
+
  Public Sub New(ByVal value As Integer)
+
    If value > MaxValue Then Throw New ArgumentOutOfRangeException("value", value, "扱える値の最大値を超えています")
+
    If value < MinValue Then Throw New ArgumentOutOfRangeException("value", value, "扱える値の最小値を超えています")
+

          
+
    Me.Value = value
+
  End Sub
+

          
+
  ' Integer型への型変換 (拡大変換、暗黙の型変換を許可)
+
  Public Shared Widening Operator CType(ByVal val As Int24) As Integer
+
    Return val.Value
+
  End Operator
+

          
+
  ' Short型への型変換 (縮小変換、明示的な型変換を強制)
+
  Public Shared Narrowing Operator CType(ByVal val As Int24) As Short
+
    Return CShort(val.Value)
+
  End Operator
+
End Structure
+

          
+
Module Sample
+
  Sub Main()
+
    Dim val As New Int24(100)
+

          
+
    ' Integer型への型変換
+
    Dim i1 As Integer = val ' 暗黙の型変換
+
    Dim i2 As Integer = CType(val, Integer) ' 明示的な型変換
+
    Dim i3 As Integer = CInt(val) ' 明示的な型変換
+

          
+
    ' Short型への型変換
+
    'Dim s1 As Short = val ' 暗黙の型変換は行えないのでコンパイル時にエラーとなる
+
    ' error BC30512: Option Strict On で 'Int24' から 'Short' への暗黙の型変換はできません。
+
    Dim s2 As Short = CType(val, Short) ' 明示的な型変換
+
    Dim s3 As Short = CShort(val) ' 明示的な型変換
+
  End Sub
+
End Module
+
}}
+

          
+
上記の例からもわかるように、演算子プロシージャで型変換が定義されていれば``CInt``や``CShort``など``CType``関数以外での型変換も行えるようになります。
+

          
+
``CType``演算子をオーバーロードする以外で型変換を定義する方法については[[programming/netfx/conversion/1_userdefined]]を参照してください。
+

          
+

          
+
*演算子プロシージャ宣言の分離
+
演算子プロシージャは引数として使用されている型の中で宣言されている必要があります。 従って、演算子プロシージャの引数の型と無関係なクラス・構造体で演算子プロシージャを宣言することはできません。 例えば、次のように演算子プロシージャだけをまとめたクラスを作成することはできません。
+

          
+
#code(vb){{
+
Structure X
+
End Structure
+

          
+
' 演算子プロシージャの引数に型Operatorsが含まれていないためコンパイルエラーとなる
+
Class Operators
+
  ' error BC33021: このバイナリ演算子のパラメーターのうち、少なくとも 1 つが型 'Operators' を含んでいなければなりません。
+
  Public Shared Operator +(ByVal left As X, ByVal right As X) As X
+
    Return New X()
+
  End Operator
+

          
+
  ' error BC33021: このバイナリ演算子のパラメーターのうち、少なくとも 1 つが型 'Operators' を含んでいなければなりません。
+
  Public Shared Operator -(ByVal left As X, ByVal right As X) As X
+
    Return New X()
+
  End Operator
+
End Class
+
}}
+

          
+
#code(vb){{
+
Structure X
+
End Structure
+

          
+
Structure Y
+
End Structure
+

          
+
' 演算子プロシージャの引数または戻り値に型Operatorsが含まれていないためコンパイルエラーとなる
+
Class Operators
+
  ' XからYへの型変換演算子
+
  ' error BC33022: この変換演算子のパラメーター型または戻り値の型は、含んでいる型 'Operators' でなければなりません。
+
  Public Shared Widening Operator CType(ByVal val As X) As Y
+
    Return New Y()
+
  End Operator
+

          
+
  ' YからXへの型変換演算子
+
  ' error BC33022: この変換演算子のパラメーター型または戻り値の型は、含んでいる型 'Operators' でなければなりません。
+
  Public Shared Widening Operator CType(ByVal val As Y) As X
+
    Return New X()
+
  End Operator
+
End Class
+
}}
+

          
+
型変換の演算子プロシージャは引数または戻り値の型のどちらかが一致する型の中で宣言されていればよいので、相互に型変換可能な演算子を定義する場合はどちらか一方の型に型変換の演算子プロシージャをまとめることができます。
+

          
+
#code(vb){{
+
Structure X
+
End Structure
+

          
+
Structure Y
+
  ' XからYへの型変換演算子
+
  Public Shared Widening Operator CType(ByVal val As X) As Y
+
    Return New Y()
+
  End Operator
+

          
+
  ' YからXへの型変換演算子
+
  Public Shared Widening Operator CType(ByVal val As Y) As X
+
    Return New X()
+
  End Operator
+
End Structure
+
}}
+

          
+
クラス・構造体本体の実装と演算子プロシージャを分離したい場合は、``Partial``キーワードを使うこともできます。
+

          
+
#code(vb){{
+
' 構造体X本体の実装
+
Structure X
+
  '    :
+
  '    :
+
  '    :
+
  '    :
+
End Structure
+

          
+
' 構造体Xの演算子プロシージャの実装
+
Partial Structure X
+
  Public Shared Operator +(ByVal left As X, ByVal right As X) As X
+
    Return New X()
+
  End Operator
+

          
+
  Public Shared Operator -(ByVal left As X, ByVal right As X) As X
+
    Return New X()
+
  End Operator
+
End Structure
+
}}
+

          
+

          
+

          
+

          
+