演算子のオーバーロード

演算子の「上書き」

クラスのインスタンスはただの参照型の変数なので、インスタンス同士で足し算などはできません。


class MyPoint
{
    public int X;
    public int Y;

    public MyPoint() { }
    public MyPoint(int x, int y)
    {
        X = x;
        Y = y;
    }
}

static void Main(string[] args)
{
    MyPoint p1 = new MyPoint(1, 2);
    MyPoint p2 = new MyPoint(3, 4);

    //エラー
    MyPoint p3 = p1 + p2;
}      

しかし上記のようなコードが書けるといろいろと便利です。
そのためには演算子のオーバーロードを行います。


class MyPoint
{
    public int X;
    public int Y;

    public MyPoint() { }
    public MyPoint(int x, int y)
    {
        X = x;
        Y = y;
    }

    //+演算子のオーバーロード
    public static MyPoint operator +(MyPoint p1, MyPoint p2)
    {
        return new MyPoint(p1.X + p2.X, p1.Y + p2.Y);
    }
}

static void Main(string[] args)
{
    MyPoint p1 = new MyPoint(1, 2);
    MyPoint p2 = new MyPoint(3, 4);

    MyPoint p3 = p1 + p2;

    Console.WriteLine("X={0}, Y={1}", p3.X, p3.Y);

    Console.ReadLine();
}
X=4, Y=6

演算子のオーバーロードは、上記のように使用できない演算子を使用できるようにするほか、使用できる演算子の機能を置き換えることもできます。
しかし本来の意味を変えてしまうようなオーバーロードは避けるべきです。
(+演算子で掛け算をするとか)

演算子のオーバーロードの書式

演算子のオーバーロードは自作クラス内で以下の書式で行います。


public static 戻り値の型 operator 演算子(引数リスト)
{
    //変換処理
}

publicstaticは必須となっています。
戻り値の型は比較演算子とtrue、false演算子の場合はbool型、それ以外の演算子の場合は自由です。

オーバーロードする演算子が単項演算子ならば引数はひとつ、二項演算子なら引数はふたつとなります。
例えば「+」演算子は計算に値がふたつ必要なので、引数もふたつ必要となります。

単項演算子とは例えば「-2」のマイナス記号のように、値がひとつだけで使用できる演算子です。

+演算子は単項と二項の両方が存在するので、両方を定義したい場合は以下のように引数の数を変えて定義します。


class MyPoint
{
    public int X;
    public int Y;

    public MyPoint() { }
    public MyPoint(MyPoint p)
    {
        X = p.X;
        Y = p.Y;
    }
    public MyPoint(int x, int y)
    {
        X = x;
        Y = y;
    }

    //単項+演算子のオーバーロード
    public static MyPoint operator +(MyPoint p)
    {
        return new MyPoint(p);
    }
    
    //二項+演算子のオーバーロード
    public static MyPoint operator +(MyPoint p1, MyPoint p2)
    {
        return new MyPoint(p1.X + p2.X, p1.Y + p2.Y);
    }
}

オーバーロードが可能な演算子は以下です。

単項演算子 +、-、!、~、++、--、true、false
二項演算子 +、-、*、/、%、&、|、^、<<、>>
比較演算子 ==、!=、<、>、<=、>=

「+=」や「/=」などの複合代入演算子は明示的なオーバーロードはできませんが、「+」や「/」の二項演算子をオーバーロードすると使用できるようになります。

他の型との演算

+や/などの二項演算子は、他のデータ型との演算も定義可能です。


private class SimpleClass
{
    string name;

    public SimpleClass(string s)
    {
        name = s;
    }

    public static SimpleClass operator +(SimpleClass sc1, SimpleClass sc2)
    {
        return new SimpleClass(sc1.name + sc2.name);
    }

    public static SimpleClass operator +(SimpleClass sc, string s)
    {
        return new SimpleClass(sc.name + s);
    }

    public static SimpleClass operator +(string s, SimpleClass sc)
    {
        return new SimpleClass(s + sc.name);
    }
}

上記のようにすると、「自クラス+自クラス」「自クラス+string型」「string型+自クラス」の三パターンの演算が可能になります。

自クラスのインスタンスは必ずひとつは引数に取る必要があるので、「string型+string型」のようなものは定義できません。

等価演算子、非等価演算子

等価演算子(==)をオーバーロードする場合は非等価演算子(!=)もオーバーロードする必要があります。
(逆も然り)


private class SimpleClass
{
    int id;
    string name;

    public SimpleClass(int id, string name)
    {
        this.id = id;
        this.name = name;
    }

    public static bool operator ==(SimpleClass sc1, SimpleClass sc2)
    {
        if (sc1 == null || sc2 == null)
            return false;
        return sc1.id == sc2.id && sc1.name == sc2.name;
    }

    public static bool operator !=(SimpleClass sc1, SimpleClass sc2)
    {
        return !(sc1 == sc2);
    }
}

まず等価演算子をオーバーロードし、それを利用して非等価演算子をオーバーロードします。
こうすると個別に処理を書くより簡単ですし間違いがありません。

Equals、GetHashCodeのオーバーライド

等価演算子をオーバーロードする場合、EqualsGetHashCodeと言うメソッドをオーバーライドすることが推奨されます。
これは必須ではありませんが、等価演算子とEqualsメソッドの動作が異なるのは好ましくありませんし、GetHashCodeメソッドはDictionaryクラスなどで利用されているので、うまく動作しなくなる恐れがあります。

C#ではすべてのデータ型はObject型から派生しています。
Equals、GetHashCodeメソッドはObject型で定義されています。
これらを自作クラスでオーバーライドしない場合、Object型のEquals、GetHashCodeメソッドが使用されます。


private class SimpleClass
{
    int id;
    string name;

    public SimpleClass(int id, string name)
    {
        this.id = id;
        this.name = name;
    }

    public static bool operator ==(SimpleClass sc1, SimpleClass sc2)
    {
        if (sc1 == null || sc2 == null)
            return false;
        return sc1.id == sc2.id && sc1.name == sc2.name;
    }

    public static bool operator !=(SimpleClass sc1, SimpleClass sc2)
    {
        return !(sc1 == sc2);
    }

    public override bool Equals(object obj)
    {
        if (obj is SimpleClass)
            return this == (SimpleClass)obj;
        return false;
    }

    public override int GetHashCode()
    {
        return id ^ name.GetHashCode();
    }
}

Equalsメソッドは二つのオブジェクトが等しいか否かを返します。
等価演算子と同じですが、引数がobject型なのでまず自クラスか否かをチェックしてから比較します。

GetHashCodeメソッドは同一のデータを持つオブジェクト(Equalsメソッドでtrueになる場合)からは常に同一の値(int型)が返されるメソッドです。
(ハッシュ値という。違うデータから同じハッシュ値が返ってくるのは問題ない)
これは比較対象にするフィールドのハッシュ値を^演算子(XOR)でビット演算したものを返します。
組み込み型(string型など)はGetHashCodeメソッドを持っていますので、これを利用します。
int型もGetHashCodeメソッドを持っていますが、格納されている値と同じ値が返されるので使用する必要はありません。
(使用しても問題はありません)

true、false演算子

true、false演算子をオーバーロードすると、インスタンスをif文などの条件式に指定できます。
true演算子をオーバーロードした場合はfalse演算子もオーバーロードする必要があります。
(逆も然り)


private class SimpleClass
{
    int id;
    string name;

    public SimpleClass(int id, string name)
    {
        this.id = id;
        this.name = name;
    }

    public static bool operator true(SimpleClass sc)
    {
        //idが0の場合は偽とする
        return sc.id != 0;
    }
    public static bool operator false(SimpleClass sc)
    {
        return sc.id == 0;
    }
}

static void Main(string[] args)
{
    SimpleClass sc1 = new SimpleClass(0, "aaa");
    SimpleClass sc2 = new SimpleClass(1, "bbb");

    if (sc1)
        Console.WriteLine("sc1はtrue");
    else
        Console.WriteLine("sc1はfalse");

    if (sc2)
        Console.WriteLine("sc2はtrue");
    else
        Console.WriteLine("sc2はfalse");

    Console.ReadLine();
}
sc1はfalse
sc2はtrue

キャスト演算子のオーバーロード

型変換のためのキャスト演算子もオーバーロード可能です。
キャスト演算子のオーバーロードは以下のいずれかの形式で行います。


public static explicit operator 変換先の型(変換元の型 引数名)
{
    //変換処理
}

public static implicit operator 変換先の型(変換元の型 引数名)
{
    //変換処理
}

explicitキーワードでのオーバーロードの場合は明示的な型変換が必要です
implicitキーワードでのオーバーロードの場合は暗黙的な型変換が可能です。

暗黙的な型変換はプログラマが意図しないところで変換が行われるおそれがあるので、explicitキーワードでのオーバーロードの方が良いでしょう。


class SimpleClass
{
    public int num;

    public SimpleClass(int n)
    {
        num = n;
    }

    //SimpleClassをintに変換する
    public static explicit operator SimpleClass(int n)
    {
        return new SimpleClass(n);
    }

    //intをSimpleClassに変換する
    public static explicit operator int(SimpleClass sc)
    {
        return sc.num;
    }
}

static void Main(string[] args)
{
    int num1 = 5;

    SimpleClass sc = (SimpleClass)num1;
    int num2 = (int)sc;

    Console.WriteLine(sc.num);
    Console.WriteLine(num2);

    Console.ReadLine();
}
5
5

キャスト演算子はオーバーロードが可能ですが、as演算子のオーバーロードはできません。