ryota2357

[C#] 比較の定義 (IComparable, IComparer, Comparison, Comparer)

投稿日:

C#にて、比較に関係する

  • IComparable / IComparable<T>
  • IComparer / IComparer<T>
  • Comparison<T>
  • Comparer<T>

について整理しました。

まとめ

ものすごく大雑把に

IComparable / IComparable<T> は「自身と他」の比較
IComparer / IComparer<T> は「他と他」の比較
Comparison<T> は比較のデリゲート
Comparer<T> は比較の抽象クラス

1 つずつみていきます。

IComparable / IComparable<T>

IComparable

値型またはクラスで実装する、インスタンスの並べ替えを目的とする型固有の汎用比較メソッドを定義します。

IComparable<T>

インスタンスの並べ替えなどを目的とし、型固有の比較メソッドを作成するために値型またはクラスで実装する、汎用の比較メソッドを定義します。

「自身と他のオブジェクト」との比較を定義するインターフェースです。

また、CompareTo(Object) / CompareTo(T) メソッドを実装させます。

使用例

TestComp というクラスを作り、昇順に並べ替えてみました。

IComparableを使った場合
public class Program {
    public static void Main() {
        var t = new List<TestComp>() {
            new TestComp(10), new TestComp(1), new TestComp(5)
        };
        t.Sort();
        foreach(var x in t) Console.Write(x.x + " "); //<-  1 5 10
        Console.WriteLine();
    }
}

class TestComp : IComparable {
    public int x;
    public TestComp(int x) {
        this.x = x;
    }

    public int CompareTo(object obj) {
        if(obj == null) throw new ArgumentNullException();
        if(!(obj is TestComp)) throw new ArgumentException();

        var val = obj as TestComp;
        return this.x - val.x;
    }
}

ジェネリックインターフェースを使うと、例外処理やキャストをする必要が減って読みやすくなりました。

IComparable<T>を使った場合
public class Program {
    public static void Main() {
        var t = new List<TestComp>() {
            new TestComp(10), new TestComp(1), new TestComp(5)
        };
        t.Sort();
        foreach(var x in t) Console.Write(x.x + " "); //<-  1 5 10
        Console.WriteLine();
    }
}

class TestComp : IComparable<TestComp> {
    public int x;
    public TestComp(int x) {
        this.x = x;
    }

    public int CompareTo(TestComp other) {
        if(other == null) throw new ArgumentNullException();
        return this.x - other.x;
    }
}

IComparer / IComparer<T>

IComparer

2 つのオブジェクトを比較するメソッドを公開します。

IComparer<T>

2 つのオブジェクトを比較するために型が実装するメソッドを定義します。

「他のオブジェクトと、他のオブジェクト」との比較を定義するインターフェースです。
IComparable 名前が似ていますが全くの別物で「自身と」ではなく、「他と他」との比較を比較します。

また、Compare(Object, Object) / Compare<T>(T, T) メソッドを実装させます。

使用例

int の配列を降順にソートしてみます。
Array.Sort()を使います。
(List<T>.Sort()は IComparer には対応していません。ジェネリックの方は対応してます。)

ICompareを使った場合
public class Program {
    public static void Main() {
        var t = new List<int> { 10, 1, 5 };
        Array.Sort(t.ToArray(), new MyComp());
        foreach(var x in t) Console.Write(x + " "); // <-  10 5 1
        Console.WriteLine();
    }
}

class MyComp : IComparer {
    public int Compare(object x, object y) {
        var valx = (int)x;
        var valy = (int)y;
        return valy - valx;
    }
}

キャストが消えてスッキリです。
また、List<T>.Sort()が使えるようになります。

ICompare\を使った場合
public class Program {
    public static void Main() {
        var t = new List<int> { 10, 1, 5 };
        t.Sort(new MyComp());
        foreach(var x in t) Console.Write(x + " "); // <-  10 5 1
        Console.WriteLine();
    }
}

class MyComp : IComparer<int> {
    public int Compare(int x, int y) {
        return y - x;
    }
}

Comparison<T>

Comparison<T>

同じ型の 2 つのオブジェクトを比較するメソッドを表します。

他のはインターフェースでしたが、これはデリゲートです。
つまり関数であり、ラムダ式で表すことができます。

使用例

先ほどと同様に int の配列を降順ソートしてみます。
かなり短いコードになりました。

コードを見る
public class Program {
    public static void Main() {
        var t = new List<int> { 10, 1, 5 };
        t.Sort((x, y) => y-x);
        foreach(var x in t) Console.Write(x + " "); // <-  10 5 1
        Console.WriteLine();
    }
}

Comparer<T>

Comparer<T>

IComparer<T> ジェネリック インターフェイスの実装のための基本クラスを提供します。

public abstract class Comparer<T> : System.Collections.Generic.IComparer<T>, System.Collections.ICompar

IComparer と IComparer<T>の 2 つを継承するならば、このクラスを継承することでその 2 つの恩恵+便利なメソッドがついてくるよ、という感じのものだろうか。

使用例

これら 2 つの継承が必要な状況としては C++でいう比較関数の様なものを作成するのに良いかもしれないです。
非ジェネリックな ArrayList と、ジェネリックな List の両方へ対応できる様になります。
(普通 ArrayList って使わないから...)

いまいち必要な状況がわからないですが、比較関数の様なものを作ってみました。
public class Program {
    public static void Main() {
        ArrayList array = new ArrayList(new int[] {10, 1, 5});
        array.Sort(new Greater());
        foreach(var x in array) Console.Write(x + " "); // <-  10 5 1
        Console.WriteLine();
    }
}

class Greater: Comparer<int> {
  public override int Compare(int x, int y){
    return y - x;
  }
}

その他

StringComparer

大文字と小文字の区別、およびカルチャ ベースまたは序数ベースの特定の比較規則を使用する文字列比較操作を表します。

参考記事