DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

自作クラスの演算子をオーバーロードする

注意:ここで説明しているVB.NETのOperatorステートメントは、.NET Framework 2.0以降でのみ使用できます。

例えば、「1 + 1」のように2つの数字に二項+演算子を使うと、2つの数字を足した答えが得られます。文字列の場合は、2つの文字列が連結されます。このような演算子の動作を自作のクラス(あるいは構造体)に定義することもできます。

例えば、次のようなクラスがあるとします。

VB.NET
コードを隠すコードを選択
'テストのためのクラス
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
C#
コードを隠すコードを選択
//テストのためのクラス
public class TestClass
{
    public string Message;
    public int Number;
    public TestClass(string msg, int num)
    {
        Message = msg;
        Number = num;
    }
}

TestClassオブジェクトに二項+演算子を使ったとき、Messageメンバは連結され、Numberメンバの数字は足し算されるようにしてみましょう。

VB.NET
コードを隠すコードを選択
'テストのためのクラス
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
C#
コードを隠すコードを選択
//テストのためのクラス
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);
    }
}

実際に二項+演算子が使えるようになったことを確認してみます。

VB.NET
コードを隠すコードを選択
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"と表示される
C#
コードを隠すコードを選択
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つにします。

以下の例では、二項演算子の他に単項演算子と比較演算子をオーバーロードしています。

VB.NET
コードを隠すコードを選択
'テストのためのクラス 
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
C#
コードを隠すコードを選択
//テストのためのクラス
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型にキャストしてから==で比較するようにしてください。

Equals、CompareToメソッドのオーバーライド

上記の例をビルドすると、警告が出ます。比較演算子の=と<>(C#では、==と!=)をオーバーロードした場合、Equalsメソッドが返す値と=の結果が合わなくなってしまう可能性があるからです。そのようなことが無いように、=をオーバーロードしたときは、Equalsメソッドもオーバーライドして、同じ結果を返すようにします。Equalsメソッドをオーバーライドする方法については、こちらをご覧ください。

比較演算子とEqualsメソッドを実装したとき、一般的には、比較演算子の実装でEqualsメソッドを呼び出して、その結果を返します。

また、同様の理由により、比較演算子の<と>をオーバーロードしたときは、IComparableインターフェイスを実装すべきです。IComparableインターフェイスを実装する方法は、こちらで説明しています。

別の型との演算

勘違いをされる方がいるかもしれませんので、補足しておきます。上記の例の二項+演算子のオーバーロードはTestClass型同士の演算で、結果もTestClass型でしたが、必ずしもそうである必要はありません。2項演算子の場合は片方が自分自身の型であればよいのです。さらに、戻り値が別の型でも大丈夫です。

TestClassオブジェクトと数値(int型)を + すると、Numberメンバと数値を足した結果が数値で得られるようにするには、次のようにします。

VB.NET
コードを隠すコードを選択
'テストのためのクラス
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
C#
コードを隠すコードを選択
//テストのためのクラス
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インターフェイスを実装して値の大小を指定する」をご覧ください。

VB.NET
コードを隠すコードを選択
'テストのためのクラス 
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
C#
コードを隠すコードを選択
//テストのためのクラス
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メソッドを呼び出してその結果を返します。

また、対になる比較演算子の実装では、まず片方の演算子の実装を行い、もう片方の演算子の実装では、すでに実装した演算子を使用して結果を返す(上記の例のように、比較する順番を逆にする)のが一般的です。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • .NET Tipsをご利用いただく際は、注意事項をお守りください。