ソートを行うためには、並べ替えを行う個々の要素についてその大小関係を調べる必要があります。 また、独自に作成したクラスをソートするためには、その大小関係を定義する必要があります。

ここでは、コレクションとソートの基本と、大小関係を定義したり比較するためのインターフェイスであるIComparable・IComparerおよび関連するクラスについて見ていきます。 また、比較演算子のオーバーロードとこれらのインターフェイスの実装についても触れています。 ここで解説するソート方法やDictionaryのソートなどについてはソートでも解説しているので、必要に応じて参照してください。

コレクションとソート

.NET Frameworkでは、ソートを行うためのメソッドとしてArray.SortArrayList.SortList<T>.Sortなどのメソッドが用意されています。

Array.Sortメソッドは引数で指定された配列のソートを行い、ArrayList.SortメソッドとList<T>.Sortメソッドはインスタンス内に格納されている要素をソートします。 これらのメソッドは、いずれもクイックソートを行います。 以下のコードは、これらソートを行うメソッドを使った例です。

using System;
using System.Collections;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    string[] arr = new string[] {"Charlie", "Dave", "Alice", "Eve", "Bob"};
    ArrayList al = new ArrayList(arr);
    List<string> l = new List<string>(arr);

    Console.WriteLine("[Array.Sort]");

    Print(arr);

    Array.Sort(arr);

    Print(arr);

    Console.WriteLine("[ArrayList.Sort]");

    Print(al);

    al.Sort();

    Print(al);

    Console.WriteLine("[List.Sort]");

    Print(l);

    l.Sort();

    Print(l);
  }

  static void Print(IEnumerable c)
  {
    foreach (string e in c) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim arr() As String = New String() {"Charlie", "Dave", "Alice", "Eve", "Bob"}
    Dim al As New ArrayList(arr)
    Dim l As New List(Of String)(arr)

    Console.WriteLine("[Array.Sort]")

    Print(arr)

    Array.Sort(arr)

    Print(arr)

    Console.WriteLine("[ArrayList.Sort]")

    Print(al)

    al.Sort()

    Print(al)

    Console.WriteLine("[List.Sort]")

    Print(l)

    l.Sort()

    Print(l)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable)
    For Each e As String In c
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
[Array.Sort]
Charlie, Dave, Alice, Eve, Bob, 
Alice, Bob, Charlie, Dave, Eve, 
[ArrayList.Sort]
Charlie, Dave, Alice, Eve, Bob, 
Alice, Bob, Charlie, Dave, Eve, 
[List.Sort]
Charlie, Dave, Alice, Eve, Bob, 
Alice, Bob, Charlie, Dave, Eve, 

これらのメソッドでは、引数でIComparerインターフェイスやComparisonデリゲートなどを指定することで並べ替えの際の動作を変更することができるようになっています。 詳細については後述していきます。

ソートできる型

先の例では、文字列型の値をコレクションに格納してソートしましたが、int、double、DateTimeなど他の基本型でも同様にソートすることが出来ます。

using System;
using System.Collections;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    int[] arr = new int[] {3, 0, 1, 4, 2};
    ArrayList al = new ArrayList(arr);
    List<int> l = new List<int>(arr);

    Console.WriteLine("[Array.Sort]");

    Print(arr);

    Array.Sort(arr);

    Print(arr);

    Console.WriteLine("[ArrayList.Sort]");

    Print(al);

    al.Sort();

    Print(al);

    Console.WriteLine("[List.Sort]");

    Print(l);

    l.Sort();

    Print(l);
  }

  static void Print(IEnumerable c)
  {
    foreach (int e in c) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim arr() As Integer = New Integer() {3, 0, 1, 4, 2}
    Dim al As New ArrayList(arr)
    Dim l As New List(Of Integer)(arr)

    Console.WriteLine("[Array.Sort]")

    Print(arr)

    Array.Sort(arr)

    Print(arr)

    Console.WriteLine("[ArrayList.Sort]")

    Print(al)

    al.Sort()

    Print(al)

    Console.WriteLine("[List.Sort]")

    Print(l)

    l.Sort()

    Print(l)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable)
    For Each e As Integer In c
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
using System;
using System.Collections;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    double[] arr = new double[] {Math.PI, 0.0, Math.E, -1.0, 2.99e8};
    ArrayList al = new ArrayList(arr);
    List<double> l = new List<double>(arr);

    Console.WriteLine("[Array.Sort]");

    Print(arr);

    Array.Sort(arr);

    Print(arr);

    Console.WriteLine("[ArrayList.Sort]");

    Print(al);

    al.Sort();

    Print(al);

    Console.WriteLine("[List.Sort]");

    Print(l);

    l.Sort();

    Print(l);
  }

  static void Print(IEnumerable c)
  {
    foreach (double e in c) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim arr() As Double = New Double() {Math.PI, 0.0, Math.E, -1.0, 2.99e8}
    Dim al As New ArrayList(arr)
    Dim l As New List(Of Double)(arr)

    Console.WriteLine("[Array.Sort]")

    Print(arr)

    Array.Sort(arr)

    Print(arr)

    Console.WriteLine("[ArrayList.Sort]")

    Print(al)

    al.Sort()

    Print(al)

    Console.WriteLine("[List.Sort]")

    Print(l)

    l.Sort()

    Print(l)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable)
    For Each e As Double In c
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
using System;
using System.Collections;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    DateTime[] arr = new DateTime[] {DateTime.MinValue, DateTime.MaxValue, new DateTime(2010, 10, 10)};
    ArrayList al = new ArrayList(arr);
    List<DateTime> l = new List<DateTime>(arr);

    Console.WriteLine("[Array.Sort]");

    Print(arr);

    Array.Sort(arr);

    Print(arr);

    Console.WriteLine("[ArrayList.Sort]");

    Print(al);

    al.Sort();

    Print(al);

    Console.WriteLine("[List.Sort]");

    Print(l);

    l.Sort();

    Print(l);
  }

  static void Print(IEnumerable c)
  {
    foreach (DateTime e in c) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim arr() As DateTime = New DateTime() {DateTime.MinValue, DateTime.MaxValue, new DateTime(2010, 10, 10)}
    Dim al As New ArrayList(arr)
    Dim l As New List(Of DateTime)(arr)

    Console.WriteLine("[Array.Sort]")

    Print(arr)

    Array.Sort(arr)

    Print(arr)

    Console.WriteLine("[ArrayList.Sort]")

    Print(al)

    al.Sort()

    Print(al)

    Console.WriteLine("[List.Sort]")

    Print(l)

    l.Sort()

    Print(l)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable)
    For Each e As DateTime In c
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
[Array.Sort]
3, 0, 1, 4, 2, 
0, 1, 2, 3, 4, 
[ArrayList.Sort]
3, 0, 1, 4, 2, 
0, 1, 2, 3, 4, 
[List.Sort]
3, 0, 1, 4, 2, 
0, 1, 2, 3, 4,
実行結果
[Array.Sort]
3.14159265358979, 0, 2.71828182845905, -1, 299000000, 
-1, 0, 2.71828182845905, 3.14159265358979, 299000000, 
[ArrayList.Sort]
3.14159265358979, 0, 2.71828182845905, -1, 299000000, 
-1, 0, 2.71828182845905, 3.14159265358979, 299000000, 
[List.Sort]
3.14159265358979, 0, 2.71828182845905, -1, 299000000, 
-1, 0, 2.71828182845905, 3.14159265358979, 299000000, 
実行結果
[Array.Sort]
0001/01/01 0:00:00, 9999/12/31 23:59:59, 2010/10/10 0:00:00, 
0001/01/01 0:00:00, 2010/10/10 0:00:00, 9999/12/31 23:59:59, 
[ArrayList.Sort]
0001/01/01 0:00:00, 9999/12/31 23:59:59, 2010/10/10 0:00:00, 
0001/01/01 0:00:00, 2010/10/10 0:00:00, 9999/12/31 23:59:59, 
[List.Sort]
0001/01/01 0:00:00, 9999/12/31 23:59:59, 2010/10/10 0:00:00, 
0001/01/01 0:00:00, 2010/10/10 0:00:00, 9999/12/31 23:59:59, 

ソートできない型

このように、基本型ならばソートは可能なように見えます。 どのような型がソート可能となり、ソート可能とならないのか、その違いを見るために文字列をラップするクラスMyStringを作成し、先の例と同様にソートしてみます。

using System;
using System.Collections;
using System.Collections.Generic;

class MyString {
  string val;

  public MyString(string val)
  {
    this.val = val;
  }

  public override string ToString()
  {
    return val;
  }
}

class Sample {
  static void Main()
  {
    MyString[] arr = new MyString[] {
      new MyString("Charlie"),
      new MyString("Dave"),
      new MyString("Alice"),
      new MyString("Eve"),
      new MyString("Bob")
    };
    ArrayList al = new ArrayList(arr);
    List<MyString> l = new List<MyString>(arr);

    Console.WriteLine("[Array.Sort]");

    Print(arr);

    try {
      Array.Sort(arr);

      Print(arr);
    }
    catch (Exception ex) {
      Console.WriteLine(ex);
    }

    Console.WriteLine("[ArrayList.Sort]");

    Print(al);

    try {
      al.Sort();

      Print(al);
    }
    catch (Exception ex) {
      Console.WriteLine(ex);
    }

    Console.WriteLine("[List.Sort]");

    Print(l);

    try {
      l.Sort();

      Print(l);
    }
    catch (Exception ex) {
      Console.WriteLine(ex);
    }
  }

  static void Print(IEnumerable c)
  {
    foreach (MyString e in c) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections
Imports System.Collections.Generic

Class MyString
  Dim val As String

  Public Sub New(ByVal val As String)
    MyClass.val = val
  End Sub

  Public Overrides Function ToString() As String
    Return val
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim arr() As MyString = New MyString() { _
      New MyString("Charlie"), _
      New MyString("Dave"), _
      New MyString("Alice"), _
      New MyString("Eve"), _
      New MyString("Bob") _
    }
    Dim al As New ArrayList(arr)
    Dim l As New List(Of MyString)(arr)

    Console.WriteLine("[Array.Sort]")

    Print(arr)

    Try
      Array.Sort(arr)

      Print(arr)
    Catch ex As Exception
      Console.WriteLine(ex)
    End Try

    Console.WriteLine("[ArrayList.Sort]")

    Print(al)

    Try
      al.Sort()

      Print(al)
    Catch ex As Exception
      Console.WriteLine(ex)
    End Try

    Console.WriteLine("[List.Sort]")

    Print(l)

    Try
      l.Sort()

      Print(l)
    Catch ex As Exception
      Console.WriteLine(ex)
    End Try
  End Sub

  Shared Sub Print(ByVal c As IEnumerable)
    For Each e As MyString In c
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
[Array.Sort]
Charlie, Dave, Alice, Eve, Bob, 
System.InvalidOperationException: 配列にある 2 つの要素を比較できませんでした。 ---> System.ArgumentException: 少なくとも 1 つのオブジェクトで IComparable を実装しなければなりません。
   場所 System.Collections.Comparer.Compare(Object a, Object b)
   場所 System.Collections.Generic.ObjectComparer`1.Compare(T x, T y)
   場所 System.Collections.Generic.ArraySortHelper`1.SwapIfGreaterWithItems(T[] keys, IComparer`1 comparer, Int32 a, Int32 b)
   場所 System.Collections.Generic.ArraySortHelper`1.QuickSort(T[] keys, Int32 left, Int32 right, IComparer`1 comparer)
   場所 System.Collections.Generic.ArraySortHelper`1.Sort(T[] keys, Int32 index, Int32 length, IComparer`1 comparer)
   --- 内部例外スタック トレースの終わり ---
   場所 System.Collections.Generic.ArraySortHelper`1.Sort(T[] keys, Int32 index, Int32 length, IComparer`1 comparer)
   場所 System.Array.Sort[T](T[] array, Int32 index, Int32 length, IComparer`1 comparer)
   場所 System.Array.Sort[T](T[] array)
   場所 Sample.Main()
[ArrayList.Sort]
Charlie, Dave, Alice, Eve, Bob, 
System.InvalidOperationException: 配列にある 2 つの要素を比較できませんでした。 ---> System.ArgumentException: 少なくとも 1 つのオブジェクトで IComparable を実装しなければなりません。
   場所 System.Collections.Comparer.Compare(Object a, Object b)
   場所 System.Array.SorterObjectArray.SwapIfGreaterWithItems(Int32 a, Int32 b)
   --- 内部例外スタック トレースの終わり ---
   場所 System.Array.SorterObjectArray.SwapIfGreaterWithItems(Int32 a, Int32 b)
   場所 System.Array.SorterObjectArray.QuickSort(Int32 left, Int32 right)
   場所 System.Array.Sort(Array keys, Array items, Int32 index, Int32 length, IComparer comparer)
   場所 System.Collections.ArrayList.Sort(Int32 index, Int32 count, IComparer comparer)
   場所 System.Collections.ArrayList.Sort()
   場所 Sample.Main()
[List.Sort]
Charlie, Dave, Alice, Eve, Bob, 
System.InvalidOperationException: 配列にある 2 つの要素を比較できませんでした。 ---> System.ArgumentException: 少なくとも 1 つのオブジェクトで IComparable を実装しなければなりません。
   場所 System.Collections.Comparer.Compare(Object a, Object b)
   場所 System.Collections.Generic.ObjectComparer`1.Compare(T x, T y)
   場所 System.Collections.Generic.ArraySortHelper`1.SwapIfGreaterWithItems(T[] keys, IComparer`1 comparer, Int32 a, Int32 b)
   場所 System.Collections.Generic.ArraySortHelper`1.QuickSort(T[] keys, Int32 left, Int32 right, IComparer`1 comparer)
   場所 System.Collections.Generic.ArraySortHelper`1.Sort(T[] keys, Int32 index, Int32 length, IComparer`1 comparer)
   --- 内部例外スタック トレースの終わり ---
   場所 System.Collections.Generic.ArraySortHelper`1.Sort(T[] keys, Int32 index, Int32 length, IComparer`1 comparer)
   場所 System.Array.Sort[T](T[] array, Int32 index, Int32 length, IComparer`1 comparer)
   場所 System.Collections.Generic.List`1.Sort(Int32 index, Int32 count, IComparer`1 comparer)
   場所 Sample.Main()

実行結果を見て分かる通り、Sortメソッドを呼び出した時点で例外がスローされ、ソートは出来ませんでした。 何が原因でソート出来なかったのか、スローされた例外について詳しく見みます。 スローされた例外はInvalidOperationExceptionで、例外のメッセージは次のようになっています。

スローされたはInvalidOperationExceptionの例外メッセージ
System.InvalidOperationException: 配列にある 2 つの要素を比較できませんでした。 ---> System.ArgumentException: 少なくとも 1 つのオブジェクトで IComparable を実装しなければなりません。

つまり、配列(コレクション)内にある要素を比較しようとしたが、その要素がIComparableインターフェイスを実装していないことを理由にArgumentExceptionをスローしたために、InvalidOperationExceptionとなったことがソート出来なかった原因となります。 言い換えると、ソートを行うには、その要素がIComparableを実装している必要があるということです。 ここまで例に挙げたstring、int、double、DateTimeは、すべてIComparableを実装しているためソートが可能だったのです。

IComparable

それでは、ソートを行う上で必要とされるIComparableインターフェイス(System名前空間)を実装するためにIComparableについて詳しく見ていきます。 IComparableインターフェイスを実装する場合は一つのメソッドCompareToを実装しなければなりません。 このメソッドでは、一つのobject型の引数を取り、インターフェイスを実装するインスタンスと引数のオブジェクトとの大小関係に応じて次の値を返す必要があります。

IComparable.CompareToの戻り値
インスタンスと引数objの大小関係 戻り値として返す値
並べ替えたときに、現在のインスタンスの方が引数objよりもとなる
(thisはobjよりも小さい)
0より小さい値
並べ替えたときに、現在のインスタンスと引数objは同じ位置となる
(thisはobjと等しい)
0
並べ替えたときに、現在のインスタンスの方が引数objよりもとなる
(thisはobjよりも大きい)
0より大きい値

早速、上記の仕様を満たすようIComparableを実装したクラスを用意し、ソートしてみます。 次のコードでは、メンバにIDと名前を持つクラスAccountを作成し、IComparableを実装しています。

using System;
using System.Collections;

class Account : IComparable {
  int id;
  string name;

  public Account(int id, string name)
  {
    this.id = id;
    this.name = name;
  }

  // IComparable.CompareToの実装
  public int CompareTo(object obj)
  {
    // 引数がnullの場合はArgumentNullExceptionをスローする
    if (obj == null) throw new ArgumentNullException();

    // 引数をAccountに型変換
    Account other = obj as Account;

    // 引数をAccountに型変換できなかった場合はArgumentExceptionをスローする
    if (other == null) throw new ArgumentException();

    // フィールドidの値の大小関係をCompareToの戻り値とする
    if (this.id < other.id) {
      // 現在のインスタンスは、引数よりも小さい
      return -1;
    }
    else if (this.id > other.id) {
      // 現在のインスタンスは、引数よりも大きい
      return 1;
    }
    else {
      // 現在のインスタンスは、引数よりは等しい
      return 0;
    }

    // より簡単に、以下のように記述することもできる
    //return this.id - other.id;
    // また、Integer.CompareToメソッドの結果を使用するように記述することもできる
    //return this.id.CompareTo(other.id);
  }

  public override string ToString()
  {
    return string.Format("{0}:{1}", id, name);
  }
}

class Sample {
  static void Main()
  {
    Account[] arr = new Account[] {
      new Account(1, "Eve"),
      new Account(4, "Dave"),
      new Account(2, "Alice"),
      new Account(0, "Charlie"),
      new Account(3, "Bob"),
    };

    Console.WriteLine("[before sort]");

    Print(arr);

    Console.WriteLine("[after sort]");

    Array.Sort(arr);

    Print(arr);
  }

  static void Print(IEnumerable c)
  {
    foreach (Account e in c) {
      Console.WriteLine(e);
    }
  }
}
Imports System
Imports System.Collections

Class Account
  Implements IComparable

  Dim id As Integer
  Dim name As String

  Public Sub New(ByVal id As Integer, ByVal name As String)
    MyClass.id = id
    MyClass.name = name
  End Sub

  ' IComparable.CompareToの実装
  Public Function CompareTo(ByVal obj As Object) As Integer Implements IComparable.CompareTo
    ' 引数がNothingの場合はArgumentNullExceptionをスローする
    If obj Is Nothing Then Throw New ArgumentNullException()

    ' 引数をAccountに型変換
    Dim other As Account = TryCast(obj, Account)

    ' 引数をAccountに型変換できなかった場合はArgumentExceptionをスローする
    If other Is Nothing Then Throw New ArgumentException()

    ' フィールドidの値の大小関係をCompareToの戻り値とする
    If MyClass.id < other.id Then
      ' 現在のインスタンスは、引数よりも小さい
      Return -1
    Else IF MyClass.id > other.id Then
      ' 現在のインスタンスは、引数よりも大きい
      Return 1
    Else
      ' 現在のインスタンスは、引数よりは等しい
      Return 0
    End If

    ' より簡単に、以下のように記述することもできる
    'Return MyClass.id - other.id
    ' また、Integer.CompareToメソッドの結果を使用するように記述することもできる
    'Return MyClass.id.CompareTo(other.id)
  End Function

  Public Overrides Function ToString() As String
    Return String.Format("{0}:{1}", id, name)
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim arr() As Account = New Account() { _
      New Account(1, "Eve"), _
      New Account(4, "Dave"), _
      New Account(2, "Alice"), _
      New Account(0, "Charlie"), _
      New Account(3, "Bob") _
    }

    Console.WriteLine("[before sort]")

    Print(arr)

    Console.WriteLine("[after sort]")

    Array.Sort(arr)

    Print(arr)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable)
    For Each e As Account In c
      Console.WriteLine(e)
    Next
  End Sub
End Class
実行結果
[before sort]
1:Eve
4:Dave
2:Alice
0:Charlie
3:Bob
[after sort]
0:Charlie
1:Eve
2:Alice
3:Bob
4:Dave

結果を見てわかるとおり、ソートの際にInvalidOperationExceptionがスローされることもなく、Accountクラスのidフィールドの値にしたがってソート出来ていることが分かると思います。

この例ではArray.Sortメソッドを使ったため、Accountクラスのインスタンス同士のみの比較しか行われませんが、ArrayListなどを使う場合はすべて同じ型同士の比較になるとは限りません。 IComparable.CompareToメソッドは引数がobject型であるため、どのような型との比較になるかは呼び出し元次第となります。 そのため、IComparableインターフェイスを実装する側で引数の型をチェックする必要があります。 上記のコードでは異なる型との比較ではArgumentExceptionをスローするようにしていますが、場合によっては例外とはせず適切な比較結果を返すように実装する事も可能です。

また、null(Nothing)との比較についても、上記のコードではArgumentNullExceptionをスローするようにしていますが、string型のようにnullはすべてのオブジェクトよりも小さいと定義することもできます。

IComparer

IComparableに似たインターフェイスとして、.NET FrameworkにはIComparerインターフェイス(System.Collections名前空間)が用意されています。 IComparableがインターフェイスを実装するオブジェクトと他のオブジェクトとの比較処理を提供するのに対し、IComparerは任意の二つのオブジェクトの比較処理を提供するためのものです。 言い換えると、IComparableでは比較処理は常に比較される対象と結びついていますが、IComparerでは比較処理と比較される対象を分離させることが出来ます。

このことにどのような利点があるかというと、例えば先に例に挙げたAccountクラスでは名前とIDのフィールドを持っていますが、IComparableで実装したのはIDによる比較のみです。 これを名前順やIDの降順で並べ替えたりしたい場合、IComparableの実装を書き換える必要が出てきますが、IComparerを使うことで実装を書き換えずに新たな比較処理を個別に用意することができるようになります。

IComparerインターフェイスを実装する場合は一つのメソッドCompareを実装しなければなりません。 IComparable.CompareToメソッドが一つのobject型の引数を取るのに対し、IComparer.Compareメソッドでは二つのobject型の引数を取ります。 戻り値は、IComparable.CompareToメソッドと同様、引数同士の大小関係に応じて次の値を返す必要があります。

IComparer.Compareの戻り値
引数xとyの大小関係 戻り値として返す値
並べ替えたときに、引数xが引数yよりもとなる
(xはyよりも小さい)
0より小さい値
並べ替えたときに、引数xが引数yと同じ位置となる
(xはyと等しい)
0
並べ替えたときに、引数xが引数yよりもとなる
(xはyよりも大きい)
0より大きい値

早速、上記の仕様を満たすようIComparerを実装したクラスを用意し、ソートしてみます。 次のコードでは、IComparerを実装してint型の配列を昇順・降順で並べ替えるためのクラスを作成し、ソートに使っています。

using System;
using System.Collections;

// intを昇順で並べ替えるIComparerの実装
class AscendingOrderComparer : IComparer {
  public int Compare(object x, object y)
  {
    int ix = (int)x;
    int iy = (int)y;

    return ix - iy;
    // 次のように記述することもできる
    //return ix.CompareTo(iy);
  }
}

// intを降順で並べ替えるIComparerの実装
class DescendingOrderComparer : IComparer {
  public int Compare(object x, object y)
  {
    int ix = (int)x;
    int iy = (int)y;

    return iy - ix;
    // 次のように記述することもできる
    //return iy.CompareTo(ix);
    //return -ix.CompareTo(iy);
  }
}

class Sample {
  static void Main()
  {
    int[] arr = new int[] {3, 4, 2, 0, 1};

    Console.WriteLine("[before sort]");

    Print(arr);

    // AscendingOrderComparerを使ってソート
    Console.WriteLine("[AscendingOrderComparer]");

    Array.Sort(arr, new AscendingOrderComparer());

    Print(arr);

    // DescendingOrderComparerを使ってソート
    Console.WriteLine("[DescendingOrderComparer]");

    Array.Sort(arr, new DescendingOrderComparer());

    Print(arr);

    // Array.SortメソッドとArray.Reverseメソッドを使ってソート
    Console.WriteLine("[Sort + Reverse]");

    Array.Sort(arr);

    Print(arr);

    Array.Reverse(arr);

    Print(arr);
  }

  static void Print(IEnumerable c)
  {
    foreach (int e in c) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections

' Integerを昇順で並べ替えるIComparerの実装
Class AscendingOrderComparer
  Implements IComparer

  Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
    Dim ix As Integer = CInt(x)
    Dim iy As Integer = CInt(y)

    Return ix - iy
    ' 次のように記述することもできる
    'Return ix.CompareTo(iy)
  End Function
End Class

' Integerを降順で並べ替えるIComparerの実装
Class DescendingOrderComparer
  Implements IComparer

  Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
    Dim ix As Integer = CInt(x)
    Dim iy As Integer = CInt(y)

    Return iy - ix
    ' 次のように記述することもできる
    'Return iy.CompareTo(ix)
    'Return -ix.CompareTo(iy)
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim arr() As Integer = New Integer() {3, 4, 2, 0, 1}

    Console.WriteLine("[before sort]")

    Print(arr)

    ' AscendingOrderComparerを使ってソート
    Console.WriteLine("[AscendingOrderComparer]")

    Array.Sort(arr, New AscendingOrderComparer())

    Print(arr)

    ' DescendingOrderComparerを使ってソート
    Console.WriteLine("[DescendingOrderComparer]")

    Array.Sort(arr, New DescendingOrderComparer())

    Print(arr)

    ' Array.SortメソッドとArray.Reverseメソッドを使ってソート
    Console.WriteLine("[Sort + Reverse]")

    Array.Sort(arr)

    Print(arr)

    Array.Reverse(arr)

    Print(arr)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable)
    For Each e As Integer In c
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
[before sort]
3, 4, 2, 0, 1, 
[AscendingOrderComparer]
0, 1, 2, 3, 4, 
[DescendingOrderComparer]
4, 3, 2, 1, 0, 
[Sort + Reverse]
0, 1, 2, 3, 4, 
4, 3, 2, 1, 0, 

Array.Sortメソッドでは引数にIComparerを指定すると、そのIComparerを使って並べ替えが行われるようになります。 その結果、上記のように昇順・降順でのソートが行えるようになります。 なお、上記のコードでは、比較のためにIComparerを使わずArray.SortメソッドとArray.Reverseメソッドを使って降順にソートする例も記述しています。

もう少し違った例を挙げてみます。 先ほどのAccountクラスと、これを比較するためのAccountComparerクラスを用意してソートします。 AccountComparerには、比較の際に昇順・降順および名前順・ID順をオプションとして選べるようプロパティを用意します。

using System;
using System.Collections;

class Account {
  private int id;
  private string name;

  public int ID {
    get { return id; }
  }

  public string Name {
    get { return name; }
  }

  public Account(int id, string name)
  {
    this.id = id;
    this.name = name;
  }

  public override string ToString()
  {
    return string.Format("{0}:{1}", id, name);
  }
}

class AccountComparer : IComparer {
  public enum SortMode {
    ByID, // ID順でソート
    ByName, // 名前順でソート
  }

  public enum SortOrder : int {
    Ascending = +1, // 昇順でソート
    Descending = -1, // 降順でソート
  }

  private SortMode mode = SortMode.ByID;
  private SortOrder order = SortOrder.Ascending;

  public SortMode Mode {
    get { return mode; }
    set { mode = value; }
  }

  public SortOrder Order {
    get { return order; }
    set { order = value; }
  }

  public int Compare(object x, object y)
  {
    // 引数がnullの場合はArgumentNullExceptionをスローする
    if (x == null) throw new ArgumentNullException("x");
    if (y == null) throw new ArgumentNullException("y");

    Account ax = x as Account;
    Account ay = y as Account;

    // Accountクラスに型変換出来ない場合は、ArgumentExceptionをスローする
    if (ax == null) throw new ArgumentException();
    if (ay == null) throw new ArgumentException();

    switch (mode) {
      case SortMode.ByID:
        return (int)order * (ax.ID - ay.ID);

      case SortMode.ByName:
        return (int)order * string.Compare(ax.Name, ay.Name);

      default:
        throw new InvalidOperationException("未定義のSortMode");
    }
  }
}

class Sample {
  static void Main()
  {
    Account[] arr = new Account[] {
      new Account(1, "Eve"),
      new Account(4, "Dave"),
      new Account(2, "Alice"),
      new Account(0, "Charlie"),
      new Account(3, "Bob"),
    };

    AccountComparer comparer = new AccountComparer();

    Print(arr);

    Console.WriteLine("[{0} {1}]", comparer.Mode, comparer.Order);

    Array.Sort(arr, comparer);

    Print(arr);

    comparer.Mode = AccountComparer.SortMode.ByName;

    Console.WriteLine("[{0} {1}]", comparer.Mode, comparer.Order);

    Array.Sort(arr, comparer);

    Print(arr);

    comparer.Order = AccountComparer.SortOrder.Descending;

    Console.WriteLine("[{0} {1}]", comparer.Mode, comparer.Order);

    Array.Sort(arr, comparer);

    Print(arr);
  }

  static void Print(IEnumerable c)
  {
    foreach (Account e in c) {
      Console.WriteLine(e);
    }
  }
}
Imports System
Imports System.Collections

Class Account
  Private _id As Integer
  Private _name As String

  Public ReadOnly Property ID As Integer
    Get
      Return _id
    End Get
  End Property

  Public ReadOnly Property Name As String
    Get
      Return _name
    End Get
  End Property

  Public Sub New(ByVal id As Integer, ByVal name As String)
    _id = id
    _name = name
  End Sub

  Public Overrides Function ToString() As String
    Return String.Format("{0}:{1}", _id, _name)
  End Function
End Class

Class AccountComparer
  Implements IComparer

  Public Enum SortMode
    ByID ' ID順でソート
    ByName ' 名前順でソート
  End Enum

  Public Enum SortOrder As Integer
    Ascending = +1 ' 昇順でソート
    Descending = -1 ' 降順でソート
  End Enum

  Private _mode As SortMode = SortMode.ByID
  Private _order As SortOrder = SortOrder.Ascending

  Public Property Mode As SortMode
    Get
      Return _mode
    End Get
    Set(ByVal value As SortMode)
      _mode = value
    End Set
  End Property

  Public Property Order As SortOrder
    Get
      Return _order
    End Get
    Set(ByVal value As SortOrder)
      _order = value
    End Set
  End Property

  Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
    ' 引数がNothingの場合はArgumentNullExceptionをスローする
    If x Is Nothing Then Throw New ArgumentNullException("x")
    If y Is Nothing Then Throw New ArgumentNullException("y")

    Dim ax As Account = TryCast(x, Account)
    Dim ay As Account = TryCast(y, Account)

    ' Accountクラスに型変換出来ない場合は、ArgumentExceptionをスローする
    If ax Is Nothing Then Throw New ArgumentException()
    If ay Is Nothing Then Throw New ArgumentException()

    Select Case _mode
      Case SortMode.ByID
        Return CInt(_order) * (ax.ID - ay.ID)

      Case SortMode.ByName
        Return CInt(_order) * string.Compare(ax.Name, ay.Name)

      Case Else
        Throw New InvalidOperationException("未定義のSortMode")
    End Select
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim arr() As Account = New Account() { _
      New Account(1, "Eve"), _
      New Account(4, "Dave"), _
      New Account(2, "Alice"), _
      New Account(0, "Charlie"), _
      New Account(3, "Bob") _
    }

    Dim comparer As New AccountComparer()

    Print(arr)

    Console.WriteLine("[{0} {1}]", comparer.Mode, comparer.Order)

    Array.Sort(arr, comparer)

    Print(arr)

    comparer.Mode = AccountComparer.SortMode.ByName

    Console.WriteLine("[{0} {1}]", comparer.Mode, comparer.Order)

    Array.Sort(arr, comparer)

    Print(arr)

    comparer.Order = AccountComparer.SortOrder.Descending

    Console.WriteLine("[{0} {1}]", comparer.Mode, comparer.Order)

    Array.Sort(arr, comparer)

    Print(arr)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable)
    For Each e As Account In c
      Console.WriteLine(e)
    Next
  End Sub
End Class
実行結果
1:Eve
4:Dave
2:Alice
0:Charlie
3:Bob
[ByID Ascending]
0:Charlie
1:Eve
2:Alice
3:Bob
4:Dave
[ByName Ascending]
2:Alice
3:Bob
0:Charlie
4:Dave
1:Eve
[ByName Descending]
1:Eve
4:Dave
0:Charlie
3:Bob
2:Alice

この例を見てわかるとおり、比較される側であるAccountクラスでは、必ずしもIComparerを実装している必要はありません。 また、IComparerを実装する側では、比較対象に合わせて比較処理を定義することができます。

IComparable<T>

IComparable<T>インターフェイス(System名前空間)はIComparableインターフェイスのジェネリック版で、型パラメータTで指定された型との比較が可能であることを表すインターフェイスです。 IComparableと同様CompareToメソッドを実装する必要がありますが、引数の型はobjectではなく型パラメータで指定された型となるため、実装に際して型のチェックをする必要が無くなります。

以下の例は、IComparableでの例をIComparable<T>インターフェイス使ったものに書き換えたものです。

using System;
using System.Collections.Generic;

class Account : IComparable<Account> {
  int id;
  string name;

  public Account(int id, string name)
  {
    this.id = id;
    this.name = name;
  }

  // IComparable<Account>.CompareToの実装
  public int CompareTo(Account other)
  {
    // 引数がnullの場合はArgumentNullExceptionをスローする
    if (other == null) throw new ArgumentNullException("other");

    // フィールドidの値の大小関係をCompareToの戻り値とする
    return this.id - other.id;
  }

  public override string ToString()
  {
    return string.Format("{0}:{1}", id, name);
  }
}

class Sample {
  static void Main()
  {
    Account[] arr = new Account[] {
      new Account(1, "Eve"),
      new Account(4, "Dave"),
      new Account(2, "Alice"),
      new Account(0, "Charlie"),
      new Account(3, "Bob"),
    };

    Console.WriteLine("[before sort]");

    Print(arr);

    Console.WriteLine("[after sort]");

    Array.Sort(arr);

    Print(arr);
  }

  static void Print(IEnumerable<Account> c)
  {
    foreach (Account e in c) {
      Console.WriteLine(e);
    }
  }
}
Imports System
Imports System.Collections.Generic

Class Account
  Implements IComparable(Of Account)

  Dim id As Integer
  Dim name As String

  Public Sub New(ByVal id As Integer, ByVal name As String)
    MyClass.id = id
    MyClass.name = name
  End Sub

  ' IComparable(Of Account).CompareToの実装
  Public Function CompareTo(ByVal other As Account) As Integer Implements IComparable(Of Account).CompareTo
    ' 引数がNothingの場合はArgumentNullExceptionをスローする
    If other Is Nothing Then Throw New ArgumentNullException("other")

    ' フィールドidの値の大小関係をCompareToの戻り値とする
    Return MyClass.id - other.id
  End Function

  Public Overrides Function ToString() As String
    Return String.Format("{0}:{1}", id, name)
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim arr() As Account = New Account() { _
      New Account(1, "Eve"), _
      New Account(4, "Dave"), _
      New Account(2, "Alice"), _
      New Account(0, "Charlie"), _
      New Account(3, "Bob") _
    }

    Console.WriteLine("[before sort]")

    Print(arr)

    Console.WriteLine("[after sort]")

    Array.Sort(arr)

    Print(arr)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable(Of Account))
    For Each e As Account In c
      Console.WriteLine(e)
    Next
  End Sub
End Class
実行結果
[before sort]
1:Eve
4:Dave
2:Alice
0:Charlie
3:Bob
[after sort]
0:Charlie
1:Eve
2:Alice
3:Bob
4:Dave

実装は省略しますが、次のコードのように型パラメータTで指定する型を変えて複数のIComparable<T>を実装することも出来ます。

複数の型に対応するIComparable<T>を実装する
using System;

class Account : IComparable<Account>, IComparable<int>, IComparable<string> {
  // IComparable<Account>.CompareToの実装
  public int CompareTo(Account other)
  {
    // Accountとの比較処理
  }

  // IComparable<int>.CompareToの実装
  public int CompareTo(int other)
  {
    // intとの比較処理
  }
  // IComparable<string>.CompareToの実装
  public int CompareTo(string other)
  {
    // stringとの比較処理
  }
}
複数の型に対応するIComparable<T>を実装する
Imports System

Class Account
  Implements IComparable(Of Account)
  Implements IComparable(Of Integer)
  Implements IComparable(Of String)

  ' IComparable(Of Account).CompareToの実装
  Public Function CompareTo(ByVal other As Account) As Integer Implements IComparable(Of Account).CompareTo
    ' Accountとの比較処理
  End Function

  ' IComparable(Of Integer).CompareToの実装
  Public Function CompareTo(ByVal other As Integer) As Integer Implements IComparable(Of Integer).CompareTo
    ' Integerとの比較処理
  End Function

  ' IComparable(Of String).CompareToの実装
  Public Function CompareTo(ByVal other As String) As Integer Implements IComparable(Of String).CompareTo
    ' Stringとの比較処理
  End Function
End Class

IComparer<T>

IComparer<T>インターフェイス(System.Collections.Generic名前空間)はIComparerインターフェイスのジェネリック版で、型パラメータTで指定された型に特化した比較処理を提供するためのインターフェイスです。 IComparerと同様Compareメソッドを実装する必要がありますが、引数の型はobjectではなく型パラメータで指定された型となるため、実装に際して型のチェックをする必要が無くなります。

以下の例は、IComparerでの例をIComparer<T>インターフェイス使ったものに書き換えたものです。

using System;
using System.Collections.Generic;

// 昇順で並べ替えるIComparer<int>の実装
class AscendingOrderComparer : IComparer<int> {
  public int Compare(int x, int y)
  {
    return x - y;
  }
}

// 降順で並べ替えるIComparer<int>の実装
class DescendingOrderComparer : IComparer<int> {
  public int Compare(int x, int y)
  {
    return y - x;
  }
}

class Sample {
  static void Main()
  {
    int[] arr = new int[] {3, 4, 2, 0, 1};

    Console.WriteLine("[before sort]");

    Print(arr);

    // AscendingOrderComparerを使ってソート
    Console.WriteLine("[AscendingOrderComparer]");

    Array.Sort(arr, new AscendingOrderComparer());

    Print(arr);

    // DescendingOrderComparerを使ってソート
    Console.WriteLine("[DescendingOrderComparer]");

    Array.Sort(arr, new DescendingOrderComparer());

    Print(arr);
  }

  static void Print(IEnumerable<int> c)
  {
    foreach (int e in c) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections.Generic

' 昇順で並べ替えるIComparer(Of Integer)の実装
Class AscendingOrderComparer
  Implements IComparer(Of Integer)

  Public Function Compare(ByVal x As Integer, ByVal y As Integer) As Integer Implements IComparer(Of Integer).Compare
    Return x - y
  End Function
End Class

' 降順で並べ替えるIComparer(Of Integer)の実装
Class DescendingOrderComparer
  Implements IComparer(Of Integer)

  Public Function Compare(ByVal x As Integer, ByVal y As Integer) As Integer Implements IComparer(Of Integer).Compare
    Return y - x
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim arr() As Integer = New Integer() {3, 4, 2, 0, 1}

    Console.WriteLine("[before sort]")

    Print(arr)

    ' AscendingOrderComparerを使ってソート
    Console.WriteLine("[AscendingOrderComparer]")

    Array.Sort(arr, New AscendingOrderComparer())

    Print(arr)

    ' DescendingOrderComparerを使ってソート
    Console.WriteLine("[DescendingOrderComparer]")

    Array.Sort(arr, New DescendingOrderComparer())

    Print(arr)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable(Of Integer))
    For Each e As Integer In c
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
[before sort]
3, 4, 2, 0, 1, 
[AscendingOrderComparer]
0, 1, 2, 3, 4, 
[DescendingOrderComparer]
4, 3, 2, 1, 0, 

Comparison<T>

Comparison<T>デリゲート(System名前空間)は、二つのオブジェクトを比較するメソッド、つまりIComparer<T>.Compareメソッドのような比較処理を行うメソッドを表すデリゲートです。 Array.SortおよびList<T>.Sortメソッドはこのデリゲートを引数に取ることができ、ソートの際の比較処理に使用することが出来ます。

このデリゲートを用いる利点は、IComparer<T>やIComparable<T>を実装したクラスを用意しなくても、比較処理を定義したメソッドを用意するだけでソートが行える点にあります。 このデリゲートと匿名メソッドやラムダ式を組み合わせて用いることでより簡単に比較処理を記述することも出来ます。

using System;
using System.Collections.Generic;

class Sample {
  // int型の値を降順で比較するメソッド
  static int CompareDescendingOrder(int x, int y)
  {
    return y - x;
  }

  static void Main()
  {
    int[] arr = new int[] {3, 4, 2, 0, 1};

    Console.WriteLine("[before sort]");

    Print(arr);

    // Comparison<int>を指定せずにソート
    Console.WriteLine("[Sort]");

    Array.Sort(arr);

    Print(arr);

    // Comparison<int>を指定してソート
    Comparison<int> comparison = CompareDescendingOrder;

    Console.WriteLine("[Sort + Comparison<int>]");

    Array.Sort(arr, comparison);

    // 単に次のように記述することも出来る
    //Array.Sort(arr, CompareDescendingOrder);
    // 匿名メソッドを使った場合は次のように記述出来る
    //Array.Sort(arr, delegate(int x, int y) {
    //  return y, x
    //});
    // ラムダ式を使った場合は次のように記述出来る
    //Array.Sort(arr, (x, y) => y - x);

    Print(arr);
  }

  static void Print(IEnumerable<int> c)
  {
    foreach (int e in c) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  ' Integer型の値を降順で比較するメソッド
  Shared Function CompareDescendingOrder(ByVal x As Integer, ByVal y As Integer) As Integer
    Return y - x
  End Function

  Shared Sub Main()
    Dim arr() As Integer = New Integer() {3, 4, 2, 0, 1}

    Console.WriteLine("[before sort]")

    Print(arr)

    ' Comparison(Of Integer)を指定せずにソート
    Console.WriteLine("[Sort]")

    Array.Sort(arr)

    Print(arr)

    ' Comparison(Of Integer)を指定してソート
    Dim comparison As Comparison(Of Integer) = AddressOf CompareDescendingOrder

    Console.WriteLine("[Sort + Comparison(Of Integer)]")

    Array.Sort(arr, comparison)
    ' 単に次のように記述することも出来る
    'Array.Sort(arr, AddressOf CompareDescendingOrder)
    ' ラムダ式を使った場合は次のように記述出来る
    'Array.Sort(arr, Function(x, y) y - x)

    Print(arr)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable(Of Integer))
    For Each e As Integer In c
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
[before sort]
3, 4, 2, 0, 1, 
[Sort]
0, 1, 2, 3, 4, 
[Sort + Comparison(Of Integer)]
4, 3, 2, 1, 0, 

Comparer<T>

Comparer<T>クラス(System.Collections.Generic名前空間)は、IComparer非ジェネリックインターフェイスとIComparer<T>ジェネリックインターフェイスを実装する抽象クラスです。 このクラスの機能と存在意義はIComparer<T>と同じですが、IComparerとIComparer<T>の二つのインターフェイスを実装するクラスを作成しなくても、このクラスを継承して抽象メソッドであるCompareメソッドを実装するだけで同じ機能を提供することができるようになります。

ジェネリックな比較処理を提供することを考えたとき、ArrayList.Sortなど非ジェネリックなコレクションのソートでも動作するようにしたい場合は、IComparer<T>だけでなくIComparerも実装する必要があります。 Comparer<T>クラスでは引数の型のチェックなどは既に実装されているため、簡単にジェネリック・非ジェネリック両方の比較処理を提供できるようになります。

以下の例では、Comparer<T>を継承したクラスを作成し、ArrayList.Sortでのソートで使用しています。 ソートしようとしているArrayListの要素にnull(Nothing)が含まれている点に注目してください。

using System;
using System.Collections;
using System.Collections.Generic;

// int型の値を降順で並べ替えるComparer
class DescendingOrderComparer : Comparer<int> {
  public override int Compare(int x, int y)
  {
    return y - x;
  }
}

class Sample {
  static void Main()
  {
    ArrayList al = new ArrayList(new object[] {3, null, 4, 2, 0, 1});

    Console.WriteLine("[before sort]");

    Print(al);

    Console.WriteLine("[after sort]");

    al.Sort(new DescendingOrderComparer());

    Print(al);
  }

  static void Print(IEnumerable c)
  {
    foreach (object e in c) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System
Imports System.Collections
Imports System.Collections.Generic

' Integer型の値を降順で並べ替えるComparer
Class DescendingOrderComparer
  Inherits Comparer(Of Integer)

  Public Overrides Function Compare(ByVal x As Integer, ByVal y As Integer) As Integer
    Return y - x
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim al As New ArrayList(New Object() {3, Nothing, 4, 2, 0, 1})

    Console.WriteLine("[before sort]")

    Print(al)

    Console.WriteLine("[after sort]")

    al.Sort(New DescendingOrderComparer())

    Print(al)
  End Sub

  Shared Sub Print(ByVal c As IEnumerable)
    For Each e As Object In c
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
[before sort]
3, , 4, 2, 0, 1, 
[after sort]
, 4, 3, 2, 1, 0, 

なお、Comparer<T>クラスで実装されているIComparer.Compareの比較処理では、null(Nothing)はnull以外のどのような値よりも小さいとみなされます。 この動作を変えたい場合は、派生クラスでIComparer.Compareを再実装する必要があります。

Comparer.Create

.NET Framework 4.5からは、任意のComparison<T>デリゲートからComparer<T>クラスのインスタンスを作成するメソッドComparer<T>.Createが追加されました。 このメソッドを使うことにより、Comparer<T>を継承したクラスを作成しなくても、Comparison<T>デリゲートをIComparer<T>として使用することができるようになります。

このメソッドは、IComparer<T>インターフェイスはサポートしているがComparison<T>デリゲートはサポートしていないクラスやメソッドを使用する際に特に便利です。 例えばSortedListでは、コンストラクタにIComparer<T>を指定することで要素のソート順を変更することができますが、Comparison<T>デリゲートを直接指定することはできません。 そのため、既存の比較用メソッドをSortedListで使用しようとした場合、メソッドをComparer<T>を継承(もしくはIComparer<T>を実装)したクラスに書き換える必要がありますが、Comparer<T>.Createメソッドを使えばコードを一切変更することなくそのまま活用することができます。

次の例では、int型の値を降順で比較するメソッドからComparer<int>を作成し、SortedListの並び替え順として使用するようにしています。 これにより、SortedListの内容はキーを降順にした並べ替えられます。

using System;
using System.Collections.Generic;

class Sample {
  // int型の値を降順で比較するメソッド
  static int CompareDescendingOrder(int x, int y)
  {
    return y - x;
  }

  static void Main()
  {
    // メソッドからComparer<int>を作成
    Comparer<int> comparer = Comparer<int>.Create(CompareDescendingOrder);

    // 作成したComparer<int>を指定してSortedListを作成
    SortedList<int, string> idAndName = new SortedList<int, string>(comparer);

    idAndName.Add(1, "Eve");
    idAndName.Add(4, "Dave");
    idAndName.Add(2, "Alice");
    idAndName.Add(0, "Charlie");
    idAndName.Add(3, "Bob");

    // SortedListの内容を表示
    foreach (KeyValuePair<int, string> pair in idAndName) {
      Console.WriteLine("{0}:{1}", pair.Key, pair.Value);
    }
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  ' Integer型の値を降順で比較するメソッド
  Shared Function CompareDescendingOrder(ByVal x As Integer, ByVal y As Integer) As Integer
    Return y - x
  End Function

  Shared Sub Main()
    ' メソッドからComparer(Of Integer)を作成
    Dim comparer As Comparer(Of Integer) = Comparer(Of Integer).Create(AddressOf CompareDescendingOrder)

    ' 作成したComparer(Of Integer)を指定してSortedListを作成
    Dim idAndName As New SortedList(Of Integer, String)(comparer)

    idAndName.Add(1, "Eve")
    idAndName.Add(4, "Dave")
    idAndName.Add(2, "Alice")
    idAndName.Add(0, "Charlie")
    idAndName.Add(3, "Bob")

    ' SortedListの内容を表示
    For Each pair As KeyValuePair(Of Integer, String) In idAndName
      Console.WriteLine("{0}:{1}", pair.Key, pair.Value)
    Next
  End Sub
End Class
実行結果
4:Dave
3:Bob
2:Alice
1:Eve
0:Charlie

StringComparer

StringComparerクラス(System名前空間)は、文字列比較に特化したIComparerの実装です。 大文字小文字を無視した比較、現在のカルチャ・インバリアントカルチャの規則に基づいた比較などを行う実装が用意されています。 詳細については、StringComparerについての個別の解説を参照してください。

using System;

class Sample {
  static void Main()
  {
    string[] arr = new string[] {"亜", "絵", "井", "尾", "宇"};

    Print(arr);

    Console.WriteLine("[StringComparer.CurrentCulture]");

    Array.Sort(arr, StringComparer.CurrentCulture);

    Print(arr);

    Console.WriteLine("[StringComparer.InvariantCulture]");

    Array.Sort(arr, StringComparer.InvariantCulture);

    Print(arr);
  }

  static void Print(string[] arr)
  {
    foreach (string e in arr) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
Imports System

Class Sample
  Shared Sub Main()
    Dim arr() As String = New String() {"亜", "絵", "井", "尾", "宇"}

    Print(arr)

    Console.WriteLine("[StringComparer.CurrentCulture]")

    Array.Sort(arr, StringComparer.CurrentCulture)

    Print(arr)

    Console.WriteLine("[StringComparer.InvariantCulture]")

    Array.Sort(arr, StringComparer.InvariantCulture)

    Print(arr)
  End Sub

  Shared Sub Print(ByVal arr() As String)
    For Each e As Object In arr
      Console.Write("{0}, ", e)
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果
亜, 絵, 井, 尾, 宇, 
[StringComparer.CurrentCulture]
亜, 井, 宇, 絵, 尾, 
[StringComparer.InvariantCulture]
井, 亜, 宇, 尾, 絵,