はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#,Unity】IComparableインターフェイスを使ってオブジェクト同士を比較する

はじめに

今回はIComparableインターフェイスについての記事になります!

C#でたびたび登場する主要インターフェイスであるIComparableIComparable<T>ですが、これによってオブジェクト同士の比較が保証することができます。

docs.microsoft.com

docs.microsoft.com

詳しくみていきましょう。

IComparableインターフェイスとは

IComparableインターフェイスを実装することで比較を可能にするメソッドを提供します。

具体的に書くとIComparableを実装しているとCompareToメソッドを定義しなければなりません。

using System.Runtime.InteropServices;

[ComVisible (true)]
public interface IComparable
{
	int CompareTo (object obj);
}
public interface IComparable<in T>
{
	int CompareTo (T other);
}


IComparableIComparable<T>は二つとも似たインターフェイスですが、定義からみても機能的には大差ありません。

ただジェネリックの方はCompareToメソッドのとれる引数が制限されます

その代わりにジェネリック版の方は処理が少し早いらしく、基本はこちらが推奨されているようです。


加えてCompareToメソッドの返り値は以下のように定義されています(自前のときはしなければなりません)。

  • オブジェクトが引数で与えられたオブジェクトより前にあるなら0未満
  • オブジェクトが引数で与えられたオブジェクトと同じ位置なら0
  • オブジェクトが引数で与えられたオブジェクトより後にあるなら0より大きい
private void Start()
{
    int a = 1;
    Debug.Log(a.CompareTo(2));   // -1
    Debug.Log(a.CompareTo(1));   // 0
    Debug.Log(a.CompareTo(0));   // 1
}

使った例

3つの値の中央値を求めるコードを書いてみました。

このIComparable<T>を用いることで、intでもfloatでも中央値を求めることができます。

using System;
using UnityEngine;

public class Test : MonoBehaviour
{
    private void Start()
    {
        int a = 10;
        int b = 5;
        int c = 15;

        var median = Median<int>(a, b, c);

        Debug.Log(median);      // 10
    }

    /// <summary>
    /// 3つの値の中央値を求める。
    /// </summary>
    static T Median<T>(T a, T b, T c) where T : IComparable<T>
    {
        if (a.CompareTo(b) > 0) Swap(ref a, ref b);
        if (a.CompareTo(c) > 0) Swap(ref a, ref c);
        if (b.CompareTo(c) > 0) Swap(ref b, ref c);
        return b;
    }

    /// <summary>
    /// 参照を入れ替える(値型だと変数のコピーになってしまうため)
    /// </summary>
    private static void Swap<T>(ref T x, ref T y) where T : IComparable<T>
    {
        var tmp = x;
        x = y;
        y = tmp;
    }
}

自作クラス

次に自作クラスにIComparable<T>を実装してみましょう。

今回は「文字列の長さが長ければ大きい」という仕組みを作ってみます。

public class MyString : IComparable<MyString>
{
    public string Text { get; }

    public MyString(string text)
        => Text = text;

    public int CompareTo(MyString other)
    {
        if(other.Text.Length > Text.Length)
        {
            return -1;
        }
        else if(other.Text.Length < Text.Length)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }
}


これを使ってみるとこんな感じ。

public class Test : MonoBehaviour
{
    private void Start()
    {
        List<MyString> list = new List<MyString>();
        list.Add(new MyString("hello"));
        list.Add(new MyString("a"));
        list.Add(new MyString("I'm fine. Thank you."));

        // 昇順にソート
        list.Sort();

        Debug.Log(list[0].Text);    // a
    }
}

さいごに

Array.SortArrayList.SortList.Sort()などのメソッドで自動的に呼び出されるらしく、自前で実装すればList<T>.Sort()も勝手に使えるようになります。

特にアルゴリズム関連では重宝しそうなインターフェイスなので、是非うまく活用してみてください。

ではまた。