.NET Visual C#言語入門 > 演算子のオーバーロード
のぶ亭『プログラミングの相談窓口』 … 様々なプログラミング問題を個別対応致します |
演算子のオーバーロード
operator キーワードは、クラス宣言または構造体宣言で、演算子を宣言するために使用されます。演算子の宣言は、次の 4 つの形式のいずれかになります。
クラスの静的メソッドとして以下のようにして定義することが出来ます。
public static 戻り値の型 operator 演算子 (引数リスト) public static result-type operator unary-operator ( op-type operand ) public static result-type operator binary-operator (op-type operand, op-type2 operand2) public static implicit operator conv-type-out ( conv-type-in operand ) public static explicit operator conv-type-out ( conv-type-in operand )
パラメータ
- result-type
- 演算子の結果の型。
- unary-operator
- + - ! ~ ++ — true false のいずれか。
- op-type
- 1 番目の (または唯一の) パラメータの型。
- operand
- 1 番目の (または唯一の) パラメータの名前。
- binary-operator
- + - * / % & | ^ << >> == != > < >= <= のいずれか。
- op-type2
- 2 番目のパラメータの型。
- operand2
- 2 番目のパラメータの名前。
- conv-type-out
- 型変換演算子による変換後の型。
- conv-type-in
- 型変換演算子に入力する型。
解説
最初の 2 つの形式は、組み込み演算子をオーバーロードするユーザー定義の演算子を宣言します。すべての組み込み演算子をオーバーロードできるとは限りません。op-type と op-type2 の少なくとも 1 つが外側の型 (演算子がメンバである型) であることが必要です。これにより、たとえば、整数加算演算子を再定義しなくても済むようになります。
最後の 2 つの形式は、変換演算子を宣言しています。conv-type-in と conv-type-out のいずれかが外側の型であることが必要です。つまり、変換演算子は、外側の型から他方の型への変換、または他方の型から外側の型への変換しかできません。
演算子の宣言に使用できるのは、値パラメータだけです。ref や out などのパラメータは使用できません。
演算子宣言の前には、属性リストを記述することもできます。
使用例
有理数を処理する簡単なクラスを次に示します。このクラスは、小数の加算および乗算を実行するために、+ 演算子および * 演算子をオーバーロードします。また、小数を倍精度に変換する演算子も提供します。
// cs_keyword_operator.cs using System; class Fraction { int num; int den; public Fraction(int num, int den) { this.num = num; this.den = den; } // オーバーロード operator + public static Fraction operator +(Fraction a, Fraction b) { return new Fraction(a.num * b.den + b.num * a.den, a.den * b.den); } // オーバーロード operator * public static Fraction operator *(Fraction a, Fraction b) { return new Fraction(a.num * b.num, a.den * b.den); } public static implicit operator double(Fraction f) { return (double)f.num / f.den; } } class Test { static void Main() { Fraction a = new Fraction(1, 2); Fraction b = new Fraction(3, 7); Fraction c = new Fraction(2, 3); Console.WriteLine((double)(a * b + c)); } }
出力
0.880952380952381
オーバーロード可能な演算子
演算子の一覧とオーバーロード可能かどうかを以下に示します。
演算子 オーバーロード可否 単項演算子 +, -, !, ~, ++, --, true, false オーバーロード可能 2項演算子 +, -, *, /, %, &, |, ^, <<, >> オーバーロード可能 比較演算子 ==, !=, <, >, <=, >= オーバーロード可能 AND/OR 演算子 &&, || 直接のオーバーロード不可
&, |, true, false をオーバーロードすることで利用可能配列の添字演算子 [] インデクサとして定義することができます キャスト キャスト 型変換演算子として定義 代入演算子 +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= 直接のオーバーロード不可
対応する2項演算子をオーバーロードすることで利用可能その他演算子 =, ., ?:, ->, new, is, sizeof, typeof オーバーロード不可
true, false 演算子
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 に代入する。
- x をそのまま戻り値として返す。
後置き
- x を別の場所に保存する。
- x を引数として ++, -- 演算子を呼び出し、その結果を x に代入する。
- 別の場所に保存しておいた、 x の変更前の値を戻り値として返す。
class Counter { private int Count; public Counter(int i) { this.Count = i; } public static Counter operator ++(Counter c) { Counter tmp = new Counter(c.Count + 1); return tmp; } public override string ToString() { return this.Count.ToString(); } } class OperatorSample { public static void Main() { Counter c = new Counter(0); Console.Write(c++ + "\n"); Console.Write(c + "\n"); Console.Write(++c + "\n"); Console.Write(c + "\n"); } }
0 1 2 2
条件 AND/OR 演算子
&&, || 演算子は直接オーバーロードすることは出来ませんが、 &, | 演算子および 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("\toperator true called\n"); return b.i != 0; } public static bool operator false(Bool b) { Console.Write("\toperator false called\n"); return b.i == 0; } public static Bool operator &(Bool a, Bool b) { Console.Write("\toperator & called\n"); return new Bool(a.i & b.i); } public static Bool operator |(Bool a, Bool b) { Console.Write("\toperator | 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"); } }