注意:ここで説明しているVB.NETのOperatorステートメントは、.NET Framework 2.0以降でのみ使用できます。
例えば、「1 + 1」のように2つの数字に二項+演算子を使うと、2つの数字を足した答えが得られます。文字列の場合は、2つの文字列が連結されます。このような演算子の動作を自作のクラス(あるいは構造体)に定義することもできます。
例えば、次のようなクラスがあるとします。
'テストのためのクラス Public Class TestClass Public Message As String Public Number As Integer Public Sub New(ByVal msg As String, ByVal num As Integer) Message = msg Number = num End Sub End Class
//テストのためのクラス public class TestClass { public string Message; public int Number; public TestClass(string msg, int num) { Message = msg; Number = num; } }
TestClassオブジェクトに二項+演算子を使ったとき、Messageメンバは連結され、Numberメンバの数字は足し算されるようにしてみましょう。
'テストのためのクラス Public Class TestClass Public Message As String Public Number As Integer Public Sub New(ByVal msg As String, ByVal num As Integer) Message = msg Number = num End Sub '二項+演算子をオーバーロードする Public Shared Operator +(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As TestClass Return New TestClass(c1.Message + c2.Message, c1.Number + c2.Number) End Operator End Class
//テストのためのクラス public class TestClass { public string Message; public int Number; public TestClass(string msg, int num) { Message = msg; Number = num; } //二項+演算子をオーバーロードする public static TestClass operator +(TestClass c1, TestClass c2) { return new TestClass(c1.Message + c2.Message, c1.Number + c2.Number); } }
実際に二項+演算子が使えるようになったことを確認してみます。
Dim c1 As New TestClass("こんにちは。", 3) Dim c2 As New TestClass("さようなら。", 10) Dim c3 As TestClass = c1 + c2 Console.WriteLine(c3.Message) '"こんにちは。さようなら。"と表示される Console.WriteLine(c3.Number) '"13"と表示される
TestClass c1 = new TestClass("こんにちは。", 3); TestClass c2 = new TestClass("さようなら。", 10); TestClass c3 = c1 + c2; Console.WriteLine(c3.Message); //"こんにちは。さようなら。"と表示される Console.WriteLine(c3.Number); //"13"と表示される
このように、VB.NETではOperatorステートメント、C#ではoperatorキーワードを使うことにより、演算子をオーバーロードすることができます。
二項+演算子だけでなく、多くの演算子をオーバーロードすることができます。
VB.NETでは、二項演算子の+、-、*、/、\、&、^、And、Like、Mod、Or、Xor、単項演算子の+、-、IsFalse、IsTrue、Not、比較演算子の>>、<<、=、<>、>、>=、<、<=をオーバーロードすることができます。
C#では、二項演算子の+、-、*、/、%、&、|、^、<<、>>、単項演算子の+、-、!、~、++、--、true、false、比較演算子の==、!=、<、>、<=、>=をオーバーロードすることができます。
代入演算子の+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=はオーバーロードできませんが、対応する二項演算子の結果が使われます。
補足:単項演算子の+とは、「+10」のように、数字の前などに付けられる演算子のことです。
これらの演算子をオーバーロードする方法も、先に示した二項+演算子の方法とほぼ同じです。「+」の部分をオーバーロードする演算子記号に代え、単項演算子ではパラメータの数を1つにします。
以下の例では、二項演算子の他に単項演算子と比較演算子をオーバーロードしています。
'テストのためのクラス Public Class TestClass Public Message As String Public Number As Integer Public Sub New(ByVal msg As String, ByVal num As Integer) Message = msg Number = num End Sub '二項+演算子をオーバーロードする Public Shared Operator +(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As TestClass Return New TestClass(c1.Message + c2.Message, c1.Number + c2.Number) End Operator '単項+演算子をオーバーロードする Public Shared Operator +(ByVal c1 As TestClass) As TestClass Return New TestClass(c1.Message, +c1.Number) End Operator '比較演算子の=と<>をオーバーロードする Public Shared Operator =(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As Boolean 'Nothingの確認(構造体のようにNULLにならない型では不要) '両方Nothingか(参照元が同じか) '(c1 = c2)とすると、無限ループ If c1 Is c2 Then Return True End If 'どちらかがNothingか '(c1 = Nothing)とすると、無限ループ If (c1 Is Nothing) OrElse (c2 Is Nothing) Then Return False End If Return (c1.Message = c2.Message) AndAlso (c1.Number = c2.Number) End Operator Public Shared Operator <>(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As Boolean Return Not (c1 = c2) '(c1 <> c2)とすると、無限ループ End Operator End Class
//テストのためのクラス public class TestClass { public string Message; public int Number; public TestClass(string msg, int num) { Message = msg; Number = num; } //二項+演算子をオーバーロードする public static TestClass operator +(TestClass c1, TestClass c2) { return new TestClass(c1.Message + c2.Message, c1.Number + c2.Number); } //単項演算子インクリメント演算子をオーバーロードする public static TestClass operator ++(TestClass c1) { return new TestClass(c1.Message, c1.Number + 1); } //比較演算子の==と!=をオーバーロードする public static bool operator ==(TestClass c1, TestClass c2) { //nullの確認(構造体のようにNULLにならない型では不要) //両方nullか(参照元が同じか) //(c1 == c2)とすると、無限ループ if (object.ReferenceEquals(c1, c2)) { return true; } //どちらかがnullか //(c1 == null)とすると、無限ループ if (((object)c1 == null) || ((object)c2 == null)) { return false; } return (c1.Message == c2.Message) && (c1.Number == c2.Number); } public static bool operator !=(TestClass c1, TestClass c2) { return !(c1 == c2); //(c1 != c2)とすると、無限ループ } }
上記の例では比較演算子の=と<>(C#では、==と!=)をオーバーロードしていますが、この内どちらかだけをオーバーロードするということはできず、必ず両方をオーバーロードする必要があります。VB.NETでは=と<>の他にも、<と>、<=と>=、IsTrueとIsFalseも必ず両方をオーバーロードする必要があります。C#では==と!=の他に、<と>、<=と>=も両方をオーバーロードする必要があります。
演算子=と<>(C#では、==と!=)は、例外をスローできません。それ以外の比較演算子は、例外をスローすることがあります。
上記のコードにコメントとして記述しておきましたが、C#の==演算子の実装では、無限ループにならないように注意してください。特に、渡されたオブジェクトがNULLかを調べるためにそのまま"=="で比較できないことに注意が必要です。Object.ReferenceEqualsメソッドを使うか、Object型にキャストしてから==で比較するようにしてください。
上記の例をビルドすると、警告が出ます。比較演算子の=と<>(C#では、==と!=)をオーバーロードした場合、Equalsメソッドが返す値と=の結果が合わなくなってしまう可能性があるからです。そのようなことが無いように、=をオーバーロードしたときは、Equalsメソッドもオーバーライドして、同じ結果を返すようにします。Equalsメソッドをオーバーライドする方法については、こちらをご覧ください。
比較演算子とEqualsメソッドを実装したとき、一般的には、比較演算子の実装でEqualsメソッドを呼び出して、その結果を返します。
また、同様の理由により、比較演算子の<と>をオーバーロードしたときは、IComparableインターフェイスを実装すべきです。IComparableインターフェイスを実装する方法は、こちらで説明しています。
勘違いをされる方がいるかもしれませんので、補足しておきます。上記の例の二項+演算子のオーバーロードはTestClass型同士の演算で、結果もTestClass型でしたが、必ずしもそうである必要はありません。2項演算子の場合は片方が自分自身の型であればよいのです。さらに、戻り値が別の型でも大丈夫です。
TestClassオブジェクトと数値(int型)を + すると、Numberメンバと数値を足した結果が数値で得られるようにするには、次のようにします。
'テストのためのクラス Public Class TestClass Public Message As String Public Number As Integer Public Sub New(ByVal msg As String, ByVal num As Integer) Message = msg Number = num End Sub '二項+演算子をオーバーロードする Public Overloads Shared Operator +( _ ByVal c1 As TestClass, ByVal c2 As TestClass) As TestClass Return New TestClass(c1.Message + c2.Message, c1.Number + c2.Number) End Operator 'さらにオーバーロードして、数値との足し算を可能にする Public Overloads Shared Operator +( _ ByVal c1 As TestClass, ByVal num As Integer) As Integer Return c1.Number + num End Operator '数値が先に来るパターンもオーバーロードしておく Public Overloads Shared Operator +( _ ByVal num As Integer, ByVal c1 As TestClass) As Integer Return num + c1.Number End Operator End Class
//テストのためのクラス public class TestClass { public string Message; public int Number; public TestClass(string msg, int num) { Message = msg; Number = num; } //二項+演算子をオーバーロードする public static TestClass operator +(TestClass c1, TestClass c2) { return new TestClass(c1.Message + c2.Message, c1.Number + c2.Number); } //さらにオーバーロードして、数値との足し算を可能にする public static int operator +(TestClass c1, int num) { return c1.Number + num; } //数値が先に来るパターンもオーバーロードしておく public static int operator +(int num, TestClass c1) { return num + c1.Number; } }
補足として、全ての比較演算子をオーバーロードした例を示します。ここではEqualsとIComparableの実装も行っていますが、これらについては、「Equalsメソッドをオーバーライドする」と「IComparableインターフェイスを実装して値の大小を指定する」をご覧ください。
'テストのためのクラス Public Class TestClass Implements System.IComparable Public Message As String Public Number As Integer Public Sub New(ByVal msg As String, ByVal num As Integer) Message = msg Number = num End Sub 'objと自分自身が等価のときはtrueを返す Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean If obj Is Nothing OrElse Not Me.GetType() Is obj.GetType() Then Return False End If Dim c As TestClass = DirectCast(obj, TestClass) Return (Me.Number = c.Number) AndAlso (Me.Message = c.Message) End Function 'Equalsがtrueを返すときに同じ値を返す Public Overloads Overrides Function GetHashCode() As Integer Return Me.Number Xor Me.Message.GetHashCode() End Function '自分自身がotherより小さいときはマイナスの数、大きいときはプラスの数、 '同じときは0を返す Public Function CompareTo(ByVal other As Object) As Integer _ Implements System.IComparable.CompareTo If other Is Nothing Then Return 1 End If If Not Me.GetType() Is other.GetType() Then Throw New ArgumentException() End If Return Me.Number.CompareTo(DirectCast(other, TestClass).Number) End Function '比較演算子の==と!=をオーバーロードする Public Shared Operator =(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As Boolean 'nullの確認 If c1 Is Nothing Then Return (c2 Is Nothing) End If If c2 Is Nothing Then Return False End If 'Equalsメソッドを呼び出す Return c1.Equals(c2) End Operator Public Shared Operator <>(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As Boolean '"=="の反対を返す Return Not (c1 = c2) End Operator '比較演算子の<と>をオーバーロードする Public Shared Operator <(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As Boolean 'nullの確認 If c1 Is Nothing OrElse c2 Is Nothing Then Throw New ArgumentNullException() End If 'CompareToメソッドを呼び出す Return (c1.CompareTo(c2) < 0) End Operator Public Shared Operator >(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As Boolean '逆にして"<"で比較 Return (c2 < c1) End Operator '比較演算子の<=と>=をオーバーロードする Public Shared Operator <=(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As Boolean 'nullの確認 If c1 Is Nothing OrElse c2 Is Nothing Then Throw New ArgumentNullException() End If 'CompareToメソッドを呼び出す Return (c1.CompareTo(c2) <= 0) End Operator Public Shared Operator >=(ByVal c1 As TestClass, ByVal c2 As TestClass) _ As Boolean '逆にして"<="で比較 Return (c2 <= c1) End Operator End Class
//テストのためのクラス public class TestClass : System.IComparable { public string Message; public int Number; public TestClass(string msg, int num) { Message = msg; Number = num; } //objと自分自身が等価のときはtrueを返す public override bool Equals(object obj) { if ((object)obj == null || this.GetType() != obj.GetType()) { return false; } TestClass c = (TestClass)obj; return (this.Number == c.Number) && (this.Message == c.Message); } //Equalsがtrueを返すときに同じ値を返す public override int GetHashCode() { return this.Number ^ this.Message.GetHashCode(); } //自分自身がotherより小さいときはマイナスの数、大きいときはプラスの数、 //同じときは0を返す public int CompareTo(object other) { if ((object)other == null) return 1; if (this.GetType() != other.GetType()) throw new ArgumentException(); return this.Number.CompareTo(((TestClass)other).Number); } //比較演算子の==と!=をオーバーロードする public static bool operator ==(TestClass c1, TestClass c2) { //nullの確認 if ((object)c1 == null) { return ((object)c2 == null); } if ((object)c2 == null) { return false; } //Equalsメソッドを呼び出す return c1.Equals(c2); } public static bool operator !=(TestClass c1, TestClass c2) { //"=="の反対を返す return !(c1 == c2); } //比較演算子の<と>をオーバーロードする public static bool operator <(TestClass c1, TestClass c2) { //nullの確認 if ((object)c1 == null || (object)c2 == null) { throw new ArgumentNullException(); } //CompareToメソッドを呼び出す return (c1.CompareTo(c2) < 0); } public static bool operator >(TestClass c1, TestClass c2) { //逆にして"<"で比較 return (c2 < c1); } //比較演算子の<=と>=をオーバーロードする public static bool operator <=(TestClass c1, TestClass c2) { //nullの確認 if ((object)c1 == null || (object)c2 == null) { throw new ArgumentNullException(); } //CompareToメソッドを呼び出す return (c1.CompareTo(c2) <= 0); } public static bool operator >=(TestClass c1, TestClass c2) { //逆にして"<="で比較 return (c2 <= c1); } }
この例のように、一般的には、等値演算子の実装ではEqualsメソッドを呼び出し、それ以外の比較演算子の実装ではCompareToメソッドを呼び出してその結果を返します。
また、対になる比較演算子の実装では、まず片方の演算子の実装を行い、もう片方の演算子の実装では、すでに実装した演算子を使用して結果を返す(上記の例のように、比較する順番を逆にする)のが一般的です。
注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。