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

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

§1 演算子のオーバーロード

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

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構造体に次のようなプロシージャを記述します。

+演算子の演算内容を定義する演算子プロシージャ
' +演算子のオーバーロード
' 引数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

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

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

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

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
実行結果
(1, 0) + (3, 4) = (4, 4)

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

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

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

*演算子をオーバーロードしてベクトルの定数倍と内積の演算を定義する例
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
実行結果
2 * (2, 1) = (4, 2)
(2, 1) * (3, 0) = 6

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



§2 オーバーロードすることができる演算子

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

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

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

§2.1 対になっている演算子のオーバーロード

つぎの演算子は二つの演算子を両方オーバーロードする必要があります。

  • 等価・不等価演算子=<>
  • 関係演算子<>および<=>=
  • 論理演算子IsTrueIsFalse

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

§3 比較演算子のオーバーロードとインターフェイスの実装

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

同様に、=<>の等価・不等価演算子とObject.Equalsメソッド・IEquatableインターフェイスについては等価演算子のオーバーロードとIEquatable<T>インターフェイスで解説しています。

§4 論理演算子のオーバーロード

§4.1 IsTrue演算子・IsFalse演算子

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

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

§4.2 論理演算子のオーバーロードおよび条件式での使用

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

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

以下はAndIsTrueIsFalse演算子をオーバーロードして条件式でAndAndAlso演算子を使用できるようにする例です。

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
実行結果
x And y = False
x AndAlso y = False
x = False

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

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

§5 型変換演算子のオーバーロード

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

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

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

拡大変換・縮小変換および明示的・暗黙的な型変換に関しては基本型の型変換でも解説しています。

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

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

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

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

CType演算子をオーバーロードする以外で型変換を定義する方法についてはユーザ定義の型変換を参照してください。

§6 演算子プロシージャ宣言の分離

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

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
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

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

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キーワードを使うこともできます。

' 構造体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