オブジェクト指向言語ではクラスを定義することで自分の思い通りの「型」を作ることが出来ます。
このような自作の型は、int や double などの組込み型と区別するため、
ユーザー定義型と呼ばれています。
ユーザー定義型の理想は、組込み型とまったく同じように扱えることです。
ユーザー定義型をあたかも組込み型であるかのように扱えるようにするため、
C#には演算子のオーバーロードというものが用意されています。
C#の組込み型には + や - などの演算子が用意されていますが、
演算子のオーバーロードを行うことで、
ユーザー定義型にも自分で演算子を定義することが出来、
組込み型と同じように扱うことができます。
このように、演算子のオーバーロードによってユーザ定義型に追加された演算子のことをユーザ定義演算子と呼びます。
演算子は operator キーワードを用いることで、
クラスの静的メソッドとして以下のようにして定義することが出来ます。
public static 戻り値の型 operator演算子 (引数リスト)
例えば、これまでに例としてあげてきた複素数クラスに加算演算子 + を定義したい場合、
以下のように書きます。
class Complex { public static Complex operator+ (Complex z, Complex w) { return new Complex(z.Re + w.Re, z.Im + w.Im); } // 残りの部分は省略 }
演算子の定義は必ず public かつ static にする必要があります。
引数リストは、
+, -, *, /
などの2項演算子なら2つ、
++, --, !, ~
などの単項演算子なら1つの引数を指定します。
演算子をオーバーロードできるといっても、C# の文法を変えてしまうようなオーバーロードはできません。
たとえば、2項演算子である / 演算子を単項演算子としてオーバーロードすることはできません。
また、引数のうち少なくとも1つの型は演算子を定義するクラス自身である必要があります。
class Complex { // ↓この2つはOK。 public static Complex operator+ (Complex z, double w) { return new Complex(z.Re + w, z.Im); } public static Complex operator+ (double z, Complex w) { return new Complex(z + w.Re, w.Im); } // ↓エラー。引数の少なくともどちらか一方は Complex でないと駄目。 public static Complex operator+ (double z, double w) { return new Complex(z + w, 0); } // 残りの部分は省略 }
演算子の一覧とオーバーロード可能かどうかを以下に示します。
| 演算子 | オーバーロード可能かどうか |
|---|---|
+, -, !, ~, ++, --, true, false
| これらの単項演算子はオーバーロード可能です。 |
+, -, *, /, %, &, |, ^, <<, >>
| これらの2項演算子はオーバーロード可能です。 |
==, !=, <, >, <=, >=
| これらの比較演算子はオーバーロード可能です。 |
&&, ||
|
これらの条件 AND/OR 演算子は直接オーバーロードすることは出来ませんが、
&, |, true, false をオーバーロードすることで利用可能になります。
|
[]
| 配列の添字演算子はインデクサとして定義することが出来ます。 詳しくは 「インデクサ」 で説明します。 |
| キャスト | キャストは型変換演算子として定義することが出来ます。 |
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
| これらの代入演算子は直接オーバーロードすることは出来ませんが、 対応する2項演算子をオーバーロードすることで利用可能になります。 |
=, ., ?:, ->, new, is, sizeof, typeof
| これらの演算子はオーバーロード出来ません。 |
+ などの演算子は特に説明は必要ないと思います。
ここでは、説明の必要になりそうな演算子のみをとりあげます。
true, false 演算子が定義された型のオブジェクトは
if や while, for, ?: などで条件式として利用することが出来ます。
true, false 演算子のどちらか一方を定義する場合、もう一方も定義する必要があります。
また、true, false 演算子の戻り値の型は bool でなければなりません。
class Bool { int i; public Bool(int i){this.i = i;} public static bool operator true(Bool b){return b.i != 0;} public static bool operator false(Bool b){return b.i == 0;} } class OperatorSample { public static void Main() { Bool b = new Bool(0); if(b) // 条件式として利用できる Console.Write("b == true"); else Console.Write("b == false"); } }
b==false
インクリメント・デクリメント演算子は一度インスタンスをコピーし、
コピー後のインスタンスの値を変更し、戻り値とします。
前置き(++x)と後置き(x++)の2つの形式がありますが、
それぞれ以下のような手順で呼び出されます。
前置き
++, -- 演算子を呼び出し、その結果を x に代入する。
後置き
++, -- 演算子を呼び出し、その結果を x に代入する。
class Counter { int i; public Counter(int i){this.i = i;} public static Counter operator ++(Counter c) { // c を直接書き換えては駄目。 // インスタンスのコピーを作る。。 Counter tmp = new Counter(c.i + 1); return tmp; } public override string ToString(){return this.i.ToString();} } class OperatorSample { public static void Main() { Counter c = new Counter(0); Console.Write(c++ + "\n"); //↑ Counter tmp = c; c = Counter.operator++(c); return tmp; Console.Write(c + "\n"); Console.Write(++c + "\n"); //↑ c = Counter.operator++(c); return c; Console.Write(c + "\n"); } }
0 1 2 2
&&, || 演算子は直接オーバーロードすることは出来ませんが、
&, | 演算子および true, false 演算子をオーバーロードすることで利用可能になります。
T 型の変数 x, y に対して、
x && y は
T.operator false(x) ? x : T.operator &(x, y) として評価されます。
すなわち、x が false として評価された場合、y は評価されません。
同様に、
x || y は
T.operator true(x) ? x : T.operator |(x, y) として評価されます。
class Bool { int i; public Bool(int i){this.i = i==0 ? 0 : 1;} public static bool operator true(Bool b) { Console.Write(" operator true called\n"); return b.i != 0; } public static bool operator false(Bool b) { Console.Write(" operator false called\n"); return b.i == 0; } public static Bool operator &(Bool a, Bool b) { Console.Write(" operator & called\n"); return new Bool(a.i & b.i); } public static Bool operator |(Bool a, Bool b) { Console.Write(" operator | called\n"); return new Bool(a.i | b.i); } } class OperatorSample { public static void Main() { Bool a = new Bool(1); Bool b = new Bool(0); Bool c; Console.Write("a && b\n"); c = a && b; Console.Write("b && a\n"); c = b && a; Console.Write("a || b\n"); c = a || b; Console.Write("b || a\n"); c = b || a; } }
a && b operator false called operator & called b && a operator false called a || b operator true called b || a operator true called operator | called
代入演算子は直接オーバーロードすることは出来ませんが、 対応する2項演算子をオーバーロードすることで利用可能になります。
例えば、+ 演算子をオーバーロードした型は、
x += y とすることで、
x = x + y と同じ結果が得られます。
型変換(cast)演算子は以下のようにして定義します。
public static explicitまたはimplicit operator 変換先の型 (変換元の型 引数名) { // 変換コード }
explicit を指定して型変換演算子を定義した場合、
明示的にキャストを行わなければ型変換を行いません
(明示的型変換)。
一方、
implicit を指定して型変換演算子を定義した場合、
型変換が必要になった時に自動的に型変換を行います
(暗黙的型変換)。
implicit を指定した場合、
意図しないところで勝手に型変換が行われてしまう可能性があるので、
出来る限り explicit を指定しましょう。
また、変換先の型と変換元の型の少なくともどちらか一方は型変換演算子を定義するクラス自身である必要があります。
using System; class Counter { int i; public Counter(int i){this.i=i;} public static explicit operator Counter (int i){return new Counter(i);} public static explicit operator int (Counter c){return c.i;} public override string ToString(){return "count="+this.i;} } class OperatorSample { public static void Main() { Counter c = new Counter(1); Console.Write((int)c + "\n"); Console.Write((Counter)2 + "\n"); } }
1 count=2
クラスの問題 1の Point 構造体を2次元ベクトルとみなして、
ベクトルの和・差を計算する演算子 + および - を追加せよ。
/// <summary> /// ベクトル和 /// </summary> /// <param name="a">点A</param> /// <param name="b">点B</param> /// <returns>和</returns> public static Point operator +(Point a, Point b) /// <summary> /// ベクトル差 /// </summary> /// <param name="a">点A</param> /// <param name="b">点B</param> /// <returns>和</returns> public static Point operator -(Point a, Point b)