今回はC#で演算子のオーバーロードを使ってみたいと思います。
C#のユーザー定義型のクラスや構造体(自分で定義したクラスや構造体)の+等の演算子をオーバーロードするとクラス同士を足したりすることが出来ます。
これが出来ると便利なのが自分で作成したクラスのインスタンス同士を+や-、++の演算子を使って独自の処理をさせることが出来ます。
C#で演算子のオーバーロードを使ってみる
説明を見るだけだと分かり辛いので実際に簡単な例で演算子のオーバーロードを使ってみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class OverLoadOperator0 : MonoBehaviour { // Start is called before the first frame update void Start() { MyClass mc1 = new MyClass(1); MyClass mc2 = new MyClass(5); MyClass mc3 = mc1 + mc2; Debug.Log(mc3); } private class MyClass { private int intValue; public MyClass(int intValue) { this.intValue = intValue; } public override string ToString() { return intValue.ToString(); } public static MyClass operator +(MyClass mc1, MyClass mc2) { return new MyClass(mc1.intValue + mc2.intValue); } } } |
上の場合はMyClassクラスでintValueフィールドを用意し、コンストラクタでクラスのインスタンス生成時に引数で受け取ったint型の値をフィールドのintValueに保持します。
またToStringメソッドのオーバーライドをしてクラスのインスタンスを表示するとintValueのフィールド値を返すようにしています(ToStringメソッドのオーバーライドは今回の演算子のオーバーロードとは何ら関係はありません)。
+演算子をMyClassクラスでオーバーロードしていて、その結果新しいMyClassクラスのインスタンスを返します。
演算子のオーバーロードの仕方
演算子をオーバーロードするにはpublic staticを付けたoperator 演算子名というメソッドに似た表記をして定義する必要があります。
先ほどの例では+演算子をoperatorキーワードを使って定義し引数で二つのMyClass型の変数を受け取ります・
2項演算子の+は二つの数を足す演算子(例えば1 + 1)なので二つの引数を受け取るようにし、必ず一つは自身の型であるMyClassを受け取るようにしなければいけません。
例えば+演算子の引数は以下のように必ず一方がMyClass型の引数を受け取れるようにします。
1 2 3 4 5 6 | public static MyClass operator +(MyClass mc1, int tempIntValue) { return new MyClass(mc1.intValue + tempIntValue); } |
サンプルを実行するとMyClassの+演算子をオーバーロードしているので、MyClass型のmc1とmc2を足すと新しいMyClass型のインスタンスを返しmc3に入れます。
それをDebug.Logで出力するとToStringメソッドをオーバーライドしているのでMyClassのintValueの値がコンソールに出力されます。
いくつかの演算子のオーバーロードをしてみる
サンプルでMyClassの+演算子をオーバーロードして確認が出来たので、新しくSwordClassクラスを作成し、様々な演算子のオーバーロードをするサンプルを作成してみます。
SwordClassの演算子のオーバーロードをしていますが、中身としてはあまり意味のないものです。(^_^;)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | using System.Collections; using System.Collections.Generic; using UnityEngine; // テスト用剣クラス public class SwordClass { public string Name { get; set; } public int AttackPower { get; set; } public SwordClass(string name = "デフォルト名", int attackPower = 1) { Name = name; AttackPower = attackPower; } public override string ToString() { return this.Name + " 攻撃力: " + this.AttackPower; } /* 単項演算子のオーバーロード */ // +演算子 public static SwordClass operator +(SwordClass sc1) { return new SwordClass("テスト", sc1.AttackPower + 1); } // -演算子 public static SwordClass operator -(SwordClass sc1) { return new SwordClass("テスト", sc1.AttackPower - 1); } // ++演算子 public static SwordClass operator ++(SwordClass sc1) { return new SwordClass("テスト", sc1.AttackPower + 1); } // --演算子 public static SwordClass operator --(SwordClass sc1) { return new SwordClass("テスト", sc1.AttackPower - 1); } /* 2項演算子 */ // +演算子 public static int operator +(SwordClass sc1, SwordClass sc2) { return sc1.AttackPower + sc2.AttackPower; } // -演算子 public static int operator -(SwordClass sc1, SwordClass sc2) { return sc1.AttackPower - sc2.AttackPower; } // *演算子 public static SwordClass operator *(SwordClass sc1, int x) { return new SwordClass("テスト", sc1.AttackPower * x); } // /演算子 public static SwordClass operator /(SwordClass sc1, int x) { return new SwordClass("テスト", sc1.AttackPower / x); } } |
SwordClassは剣の名前を表すNameプロパティと剣の強さを表すAttackPowerプロパティを持ち、インスタンスを生成する時にコンストラクタで初期値を設定します。
単項演算子のいくつかと2項演算子のいくつかをオーバーロードしています。
確認する為のクラスは以下のようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class OverLoadOperator1 : MonoBehaviour { // Start is called before the first frame update void Start() { // テスト用にインスタンスを生成 SwordClass sc1 = new SwordClass("テスト剣1", 100); Debug.Log(sc1.Name + " : " + sc1.AttackPower); SwordClass sc2 = new SwordClass("テスト剣2", 200); Debug.Log(sc2.Name + " : " + sc2.AttackPower); /* 単項演算子 */ Debug.Log((+sc1).ToString()); Debug.Log((-sc1)); // sc1やsc2に上書きされる Debug.Log((++sc1)); Debug.Log(sc2--); Debug.Log(sc2); /* 2項演算子 */ // +演算子 var sc3 = new SwordClass("新しい剣", sc1 + sc2); Debug.Log(sc3); // -演算子 var sc4 = new SwordClass(sc1.Name, sc3 - sc2); Debug.Log(sc4); // *演算子 var sc5 = sc1 * 2; Debug.Log(sc5); // /演算子 var sc6 = sc2 / 5; Debug.Log(sc6); } } |
ユーザー定義の条件付き論理演算子のオーバーロード
&&や||は直接オーバーロードが出来ないので、&と|、true、falseの演算子をそれぞれオーバーロードし実現させます。
ここら辺はC#ガイドのユーザー定義の条件付き論理演算子を参照してください。
&&演算子
C#ガイドを見ると&&の評価は以下のようにされます。
x && y は T.false(x) ? x : T.&(x, y)
なのでクラス等のfalse演算子のオーバーロードにx(クラスのインスタンス)を渡して評価し、それが真であればx、そうでなければそのクラス等の&演算子のオーバーロードにxとy(クラスのインスタンス)を渡し評価した結果を返します。
なので&&を使うにはtrue、false、&の演算子のオーバーロードを行う必要があります。
確認の為、簡単なサンプルを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class OverLoadOperator2 : MonoBehaviour { // Start is called before the first frame update void Start() { MyClass mc1 = new MyClass(0); MyClass mc2 = new MyClass(14); MyClass mc3 = new MyClass(3); Debug.Log("&&の確認 " + (mc1 && mc2)); Debug.Log("&&の確認 " + (mc2 && mc3)); // 14と3の2進数のビット毎のANDの確認 Debug.Log("ビット毎のANDの確認 " + (0b1110 & 0b0011)); } private class MyClass { private int intValue; public MyClass(int intValue = 0) { this.intValue = intValue; } public override string ToString() { return intValue.ToString(); } public static bool operator true(MyClass mc) { // 0より大きければtrueを返す return mc.intValue > 0; } public static bool operator false(MyClass mc) { // 0以下であればtrueを返す return mc.intValue <= 0; } public static MyClass operator &(MyClass mc1, MyClass mc2) { // ビット毎のANDを計算し、それを使ってMyClassのインスタンス生成 return new MyClass(mc1.intValue & mc2.intValue); } } } |
MyClassではインスタンス生成の時に引数に数値が渡された時はそれをフィールドのintValueに入れます(設定されていなければデフォルト値0を入れる)。
true演算子のオーバーロードで引数で受け取ったMyClassのintValueフィールドが0より大きければtrueを返します。
ここは分かり辛いですが、true演算子が実行された時にmc.intValueが0より大きいか判定し、大きければtrueを返し、大きくなければfalseを返すという事です。
false演算子のオーバーロードでは引数で受け取ったMyClassのインスタンスのintValueフィールドの値が0以下であればtrueを返します。
&演算子のオーバーロードでは引数で受け取った二つのMyClassのインスタンスのintValueフィールド値のビット毎のANDを計算し、それを引数として渡してMyClassインスタンスを生成し返しています。
ビット毎のANDは例えば3は2進数で表すと0011、14は2進数で表すと1110なので
0011
1110
のビット毎のAND(同じ桁が両方1の時1)は
0010
となり、これを10進数に戻すと0×2×3+0×2×2+1×2×1+0×1なので2となります。
サンプルで見ていくと、mc1 && mc2は
MyClass.false(mc1) ? mc1 : MyClass.&(mc1, mc2)
の評価になるので、MyClass.false(mc1)はfalseのオーバーロードにmc1を引数として渡してmc1のintValueが0なので結果trueが返ります。
MyClass.false(mc1)がtrueなのでmc1を実行します。
mc1でmc1.ToString()が実行されるので、0が結果として求められます。
mc2 && mc3は
MyClass.false(mc2) ? mc2 : MyClass.&(mc2, mc3)
となりMyClass.false(mc2)はfalseのオーバーロードにmc2を引数として渡してmc2のintValueが14なので結果falseが返ります。
MyClass.false(mc2)がfalseなのでMyClass.&(mc2, mc3)を実行します。
&演算子のオーバーロードでmc2とmc3のintValueフィールドのビット毎のANDを引数に新しくMyClassインスタンスを生成してそれを返しているので、new MyClass(2)をしたのと同じ結果が得られます。
||演算子
||の評価は以下のようにされます。
x || y は T.true(x) ? x : T.|(x, y)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class OverLoadOperator3 : MonoBehaviour { // Start is called before the first frame update void Start() { MyClass mc1 = new MyClass(0); MyClass mc2 = new MyClass(14); MyClass mc3 = new MyClass(3); Debug.Log("||の確認 " + (mc1 || mc2)); Debug.Log("||の確認 " + (mc2 || mc3)); Debug.Log("ビット毎のORの確認 " + (0b1110 | 0b0011)); } private class MyClass { private int intValue; public MyClass(int intValue = 0) { this.intValue = intValue; } public override string ToString() { return intValue.ToString(); } public static bool operator true(MyClass mc) { return mc.intValue > 0; } public static bool operator false(MyClass mc) { return mc.intValue <= 0; } public static MyClass operator |(MyClass mc1, MyClass mc2) { return new MyClass(mc1.intValue | mc2.intValue); } } } |
&&演算子と||演算子のサンプルは特に有用性はありません・・・・(._.)
他にもたくさんオーバーロード出来る演算子はありますが、参考サイトのC#リファレンスを参照してください。