anz blog

構造体を比較できるようにする方法

2019-01-23 #C# #Unity

ざっくりまとめ。

  • 定義した構造体を == で比較できない
  • それ、演算子をオーバーロードしてあげればできます
    • ただし、用法用量を守って正しくお使いください

です。

環境

  • Unity v2018.2.18f1

はじめに

今回紹介する方法は一歩間違ったらダークテリトリに足を踏み入れることになるので、
危険性などを正しく理解したうえでやるときはやりましょう💪

やりたいこと

自分で定義した構造体(struct)を == 演算子で比較したい!

たとえば

public struct ItemData
{
    public int id = 0;         // primary
    public string name = "";

    publid ItemData(int id, string name)
    {
        this.id = id;
        this.name = name;
    }
}

このようなデータを入れるような構造体を定義したとして...

var item1 = new ItemData(1, "アイテム1");
var item2 = new ItemData(1, "アイテム1");

if (item1 == item2) {
    // something
}

こういうふうに比較すると...

Operator '==' cannot be applied to operands of type 'ItemData' and 'ItemData'

こういう感じのエラーが吐かれてしまいます。
== 演算子は ItemData の比較では使えないという感じのいみですが、
これが struct ではなくて class であればエラーにはならずできます。

その違いは class は参照型なので比較するときに、
参照している先が同じであれば同じものである
という条件が成り立ち暗黙的に比較が可能なのだけれど、
struct の場合は値型なのでどういう条件で比較するべきなのかっていうものを明示する必要があるのです。

コンパイラーからしたら、
ItemData っていうやつなにをもって同じものだと判定したらいいの?🤔
という状態なのでエラーになるというわけです。

対策

というわけで、比較するにはどうしたらいいか?
普通に id が primary という設定なのだから、
if (item1.id == item2.id) { } でやればいいじゃん!?っていう話でもありますが、
今回例にあげたものが id というわかりやすい一意性をもったものがあるのでそうやるのが普通かと思いますが、
判定するのに必要なものが2つ、3つと増えていけば行くほど面倒くさくなりますね。
1度だけならまだしも頻出してくるとうんざりしますね...?
if (item1 == item2) { } でやりたくなりますね?

できます!

演算子をオーバーロードするということができるので、それを利用します。

public struct Item
{
    public int id = 0;
    public string name = "":

    // == 演算子のオーバーロード
    public static bool operator == (Area left, Area right)
    {
        return left.id == right.id;
    }

    public static bool operator != (Area left, Area right)
    {
        return left.id != right.id;
    }
}

(nullチェックとかなんとかはぶいてますが...)
こういうふうに演算子をオーバーロードして、何を条件にするのか?っていうのを明示してあげると、、
if (area1 == area2) { } とかけるようになります!🎉

今回は struct の比較っていうものに焦点をあてましたが、
演算子はオーバーロードすることができるのですよ!?
という紹介が主題です!(笑)

演算子のオーバーロードは sruct に限らないですし、
== のみというわけでもないので、気になった方は調べてみると良いかもです!
楽に書けるようになったりして良いかもです。

演算子のオーバーロードは黒魔術!🧙‍

上記で例にあげたように
もともとできなかったものをできるようにする
という意味で演算子をオーバーロードするのはまだ許容できますが、

演算子でもともとできることがあるのだけれど、それをオーバーロードして処理を変える...
みたいなことは黒魔術感がはんぱないので避けましょう。
どうしてもやりたい!ってなった場合、チームで開発しているのであればちゃんと周りに相談しましょう!
(多分拒否られますけど(笑))

オーバーロードして処理を変えてしまって「お!?なにこれ。。」ってなった事例が身近にありますよね?
Unity 使いの皆様ならわかるはず(笑)
心当たりがない人は、「unity null チェック」とかでググるとでてくるはずです😇

ってことで、演算子のオーバーロードをするときは限定的にしましょう。
紹介しておいてなんですが(笑)
(知っておいて使わない選択と、知らないから選択できないっていうのは同じ結果でも違うきがするので)