2010-10-14T03:08:55の更新内容

programming/netfx2/overview/sort_comparison/index.wiki.txt

current previous
1,3094 1,904
~
${smdncms:title,比較とソート(IComparable, IComparer, IEqualityComparer)}
${smdncms:title,ソートと比較(IComparable, IComparer)}
+
${smdncms:keywords,IComparable,IComparer,IEquatable,IEqualityComparer,Comparison,Sort,Equals,ソート,比較,等価,並べ替え,大小関係}
 
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
+

          
+
.NET Frameworkでは、ソートを行うためのメソッドや、並べ替えをサポートするコレクションが用意されています。 それとは別に、大小関係の比較・等価性の比較を行うためのインターフェイスが用意されています。 これらを組み合わせる事で昇順・降順でコレクションをソートしたり、大文字小文字を無視してディクショナリのキーを指定したりといったことを行うことが出来ます。
+

          
+
ここでは、大小関係の比較を行うためのインターフェイスIComparable・IComparerと、等価性の比較を行うインターフェイスIEquatable・IEqualityComparerを中心に、比較操作と関連するクラス・インターフェイスについて解説します。
+

          
+
-関連するページ
+
--[[programming/netfx2/overview/collection]]
+
--[[programming/netfx2/overview/stringoperation]]
+

          
 
#googleadunit
#googleadunit
 

        

        
~
----
*ArrayListとソート
~

          
今回はIComparableとIComparerを使ったソートについて見てみます。 それに先だって、ArrayListクラスを用いて最も簡単なソートを行ってみようと思います。 ここではArrayListの詳細な説明はしませんが、ArrayListは通常の配列とは異なり、好きなときにアイテムを追加・削除でき、動的にアイテム数を変えることができるという特徴があります。 また、このArrayListクラスから配列を作成することもできます。 それでは早速、ArrayListとそれを用いたソートのサンプルコードを組んでみます。
+
*大小関係の比較
+
**コレクションとソート
+
.NET Frameworkでは、ソートを行うためのメソッドとして&msdn(netfx,method,System.Array.Sort){Array.Sort};、&msdn(netfx,method,System.Collections.ArrayList.Sort){ArrayList.Sort};、&msdn(netfx,id,3da4abas){List<T>.Sort};などのメソッドが用意されています。
+

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

          
+
#tabpage(C#)
+
#code(cs){{
+
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();
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
 
Imports System.Collections
Imports System.Collections
+
Imports System.Collections.Generic
 

        

        
~
Class Sample
Public Class Comparison
+
  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]")
    Public Shared Function Main(ByVal args() As String) As Integer
 

        

        
~
    Print(arr)
        Dim arr As New ArrayList()
 

        

        
~
    Array.Sort(arr)
        arr.Add("GOTO Maki")
-
        arr.Add("KAGO Ai")
-
        arr.Add("TSUJI Nozomi")
-
        arr.Add("MATSUURA Aya")
-
        arr.Add("KONNO Asami")
-
        arr.Add("FUJIMOTO Miki")
 

        

        
+
    Print(arr)
 

        

        
~
    Console.WriteLine("[ArrayList.Sort]")
        Dim name As String
 

        

        
~
    Print(al)
        Console.WriteLine("   Before sorting...")
 

        

        
~
    al.Sort()
        For Each name In arr
 

        

        
~
    Print(al)
            Console.WriteLine(name)
 

        

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

        

        
~
    Print(l)
        arr.Sort()
 

        

        
~
    l.Sort()
        Console.WriteLine()
-
        Console.WriteLine("   After sorting...")
 

        

        
~
    Print(l)
        For Each name In arr
+
  End Sub
 

        

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

        

        
~
    Console.WriteLine()
        Next
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        Return 0
+
[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デリゲートなどを指定することで並べ替えの際の動作を変更することができるようになっています。 詳細については後述していきます。
    End Function
 

        

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

        

        
~
#tabpage(intのソート/C#)
#prompt(実行結果){{
~
#code(cs){{
   Before sorting...
~
using System;
GOTO Maki
~
using System.Collections;
KAGO Ai
~
using System.Collections.Generic;
TSUJI Nozomi
-
MATSUURA Aya
-
KONNO Asami
-
FUJIMOTO Miki
 

        

        
~
class Sample
   After sorting...
~
{
FUJIMOTO Miki
~
  static void Main()
GOTO Maki
~
  {
KAGO Ai
~
    int[] arr = new int[] {3, 0, 1, 4, 2};
KONNO Asami
~
    ArrayList al = new ArrayList(arr);
MATSUURA Aya
~
    List<int> l = new List<int>(arr);
TSUJI Nozomi
~

          
Press any key to continue
~
    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);
10行目から15行目でArrayListにソートすべきアイテムを追加しています。 その後のコードではソートする前にアイテムを列挙し、ソートした後にまたアイテムを列挙しています。 実際にソートを行っているのは28行目のSort()メソッドです。 実行結果のとおり、ソート前には追加された順に並んでいるのに対し、ソート後にはアイテムとして追加した文字列がアルファベット順に並んでいます。 この例ではソート結果が分かりやすいようにアルファベットを用いましたが、日本語文字などでも適切にソートされます。
 

        

        
~
    l.Sort();
*ソートできるオブジェクト・できないオブジェクト
-
先ほどはArrayListに文字列を追加しましたが、数値も追加できます。 ソースコードは割愛しますが、先ほどのソースコードにおいてアイテムを追加した部分を文字列から数値(IntegerとDouble)に変えて実行した結果です。
 

        

        
~
    Print(l);
#prompt(Integerでのソート){{
~
  }
   Before sorting...
-
3
-
1
-
4
-
1
-
5
-
9
-
2
-
7
 

        

        
~
  static void Print(IEnumerable c)
   After sorting...
~
  {
1
~
    foreach (int e in c)
1
~
    {
2
~
      Console.Write("{0}, ", e);
3
~
    }
4
~

          
5
~
    Console.WriteLine();
7
~
  }
9
~
}
Press any key to continue
 
}}
}}
+
#tabpage(intのソート/VB)
+
#code(vb){{
+
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)
#prompt(Doubleでのソート){{
-
   Before sorting...
-
3.1415927
-
1.7320508
-
2.7182818
-
1.41421356
-
2.2360679
 

        

        
~
    al.Sort()
   After sorting...
~

          
1.41421356
~
    Print(al)
1.7320508
~

          
2.2360679
~
    Console.WriteLine("[List.Sort]")
2.7182818
~

          
3.1415927
~
    Print(l)
Press any key to continue
+

          
+
    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
 
}}
}}
+
#tabpage(doubleのソート/C#)
+
#code(cs){{
+
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]");
この調子で行けばどのような型でもソートできそうな感じなのですが、実際にはそうはいきません。 次のコードはTestClassというクラスを新たに作成し、そのインスタンスをArrayListに追加してソートさせるコードです。
 

        

        
~
    Print(arr);
#code(ソート出来ない例){{
+

          
+
    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();
+
  }
+
}
+
}}
+
#tabpage(doubleのソート/VB)
+
#code(vb){{
 
Imports System
Imports System
 
Imports System.Collections
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)
Public Class TestClass
 

        

        
~
    Console.WriteLine("[ArrayList.Sort]")
    Public str As String
 

        

        
~
    Print(al)
    Public Sub New(ByVal str As String)
-
        Me.str = str
-
    End Sub
 

        

        
~
    al.Sort()
    Public Overrides Function ToString() As String
-
        Return Me.str
-
    End Function
 

        

        
+
    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
End Class
+
}}
+
#tabpage(DateTimeのソート/C#)
+
#code(cs){{
+
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);
Public Class Comparison
 

        

        
~
    Print(arr);
    Public Shared Function Main(ByVal args() As String) As Integer
 

        

        
~
    Console.WriteLine("[ArrayList.Sort]");
        Dim arr As New ArrayList()
 

        

        
~
    Print(al);
        arr.Add(New TestClass("どんな型でも"))
-
        arr.Add(New TestClass("ArrayListは"))
-
        arr.Add(New TestClass("ソートして"))
-
        arr.Add(New TestClass("くれるだろうか"))
 

        

        
+
    al.Sort();
 

        

        
~
    Print(al);
        Dim inst As TestClass
 

        

        
~
    Console.WriteLine("[List.Sort]");
        Console.WriteLine("   Before sorting...")
+

          
+
    Print(l);
+

          
+
    l.Sort();
+

          
+
    Print(l);
+
  }
+

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

          
+
    Console.WriteLine();
+
  }
+
}
+
}}
+
#tabpage(DateTimeのソート/VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections
+
Imports System.Collections.Generic
 

        

        
~
Class Sample
        For Each inst In arr
+
  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]")
            Console.WriteLine(inst)
 

        

        
~
    Print(arr)
        Next
 

        

        
~
    Array.Sort(arr)
        arr.Sort()
 

        

        
~
    Print(arr)
        Console.WriteLine()
-
        Console.WriteLine("   After sorting...")
 

        

        
~
    Console.WriteLine("[ArrayList.Sort]")
        For Each inst In arr
 

        

        
~
    Print(al)
            Console.WriteLine(inst)
 

        

        
~
    al.Sort()
        Next
 

        

        
~
    Print(al)
        Return 0
 

        

        
~
    Console.WriteLine("[List.Sort]")
    End Function
 

        

        
+
    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
End Class
 
}}
}}
+
#tabpage-end
 

        

        
+
#tabpage(intのソートの結果)
 
#prompt{{
#prompt{{
~
[Array.Sort]
   Before sorting...
~
3, 0, 1, 4, 2, 
どんな型でも
~
0, 1, 2, 3, 4, 
ArrayListは
~
[ArrayList.Sort]
ソートして
~
3, 0, 1, 4, 2, 
くれるだろうか
+
0, 1, 2, 3, 4, 
+
[List.Sort]
+
3, 0, 1, 4, 2, 
+
0, 1, 2, 3, 4,
+
}}
+
#tabpage(doubleのソートの結果)
+
#prompt{{
+
[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, 
+
}}
+
#tabpage(DateTimeのソートの結果)
+
#prompt{{
+
[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, 
+
}}
+
#tabpage-end
 

        

        
~
このように基本型ならばソートは可能なように見えます。 どのような型がソート可能となり、ソート可能とならないのか、その違いを見るために文字列をラップするクラスMyStringを作成し、先の例と同様にソートしてみます。
ハンドルされていない例外 : System.InvalidOperationException: 指定された ICompare
~

          
r が例外をスローしました。 ---> System.ArgumentException: 少なくとも 1 つのオブ
~
#tabpage(C#)
ジェクトで IComparable を実装しなければなりません。
~
#code(cs){{
   at System.Collections.Comparer.Compare(Object a, Object b)
~
using System;
   at System.SorterObjectArray.QuickSort(Int32 left, Int32 right)
~
using System.Collections;
   --- 内部例外スタック トレースの終わり ---
~
using System.Collections.Generic;
   at System.SorterObjectArray.QuickSort(Int32 left, Int32 right)
~

          
   at System.Array.Sort(Array keys, Array items, Int32 index, Int32 length, ICom
~
class MyString
parer comparer)
~
{
   at System.Collections.ArrayList.Sort(Int32 index, Int32 count, IComparer comp
~
  string val;
arer)
~

          
   at System.Collections.ArrayList.Sort()
~
  public MyString(string val)
   at SampleConsoleApplication.MainModule.Main() in D:\Visual Basic .NET Program
~
  {
\SampleConsoleApplication\Module1.vb:line 148
~
    this.val = val;
Press any key to continue
+
  }
+

          
+
  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();
+
  }
+
}
 
}}
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections
+
Imports System.Collections.Generic
 

        

        
~
Class MyString
実行結果のように、Sort()メソッドを呼び出したとたん例外エラーが発生し、ソートに失敗します。 IntegerやString型は数値・文字列なので現実的に「比較」できるのですが、TestClassのような場合文字列が含まれているとはいえ、どのように比較していいのかわかりません。 そのためこのような例外エラーが発生します。
+
  Dim val As String
 

        

        
~
  Public Sub New(ByVal val As String)
しかし、この例外エラーのメッセージをよく見てみると、
~
    MyClass.val = val
 ハンドルされていない例外 : System.InvalidOperationException: 指定された IComparer が例外をスローしました。 ---> System.ArgumentException: 少なくとも 1 つのオブジェクトで IComparable を実装しなければなりません。
+
  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
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
というように書いてあります。 実はソートできるかできないかを決めるカギはこのIComparableインターフェイスにあるのです。 つまり、ソートを行うには、そのクラスがIComparableを実装していればいいのです。 Integer型のエイリアスであるSystem.Int32構造体は IComparableなどを含むいくつかのインターフェイスを実装しています。 そのためにソートができたのです。
+
[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で、例外のメッセージは次のようになっています。
*クラスにIComparableを実装する
~
 System.InvalidOperationException: 配列にある 2 つの要素を比較できませんでした。 ---> System.ArgumentException: 少なくとも 1 つのオブジェクトで IComparable を実装しなければなりません。
それでは早速IComparableインターフェイスを実装してみます。 IComparableインターフェイスを実装する場合は一つのメソッドCompareTo()を実装しなければなりません。 このメソッドの詳細は次のようなものです。
 

        

        
~
つまり、配列(コレクション)内にある要素を比較しようとしたが、その要素がIComparableインターフェイスを実装していないことを理由にArgumentExceptionをスローしたために、InvalidOperationExceptionとなったことがソート出来なかった原因となります。 言い換えると、ソートを行うには、その要素がIComparableを実装している必要があるということです。 ここまで例に挙げたstring、int、double、DateTimeは、すべてIComparableを実装しているためソートが可能だったのです。
+一つのObject型を引数にとり、Integer型の値を返す。 つまりシグニチャは「Function CompareTo( ByVal obj As Object ) As Integer」
-
+objとこの(インターフェイスを実装する)インスタンスを比較し、次の値を関数の戻り値として返す。
-
++objよりこのインスタンスが大きいと比較できるときは 0より大きい値
-
++objとこのインスタンスが同じと比較できるときは 0
-
++objよりこのインスタンスが小さいと比較できるときは 0より小さい値
 

        

        
~
**IComparable
このことを考慮して、IComparableを実装したクラスの例を次に示します。 このクラスでは、名前・誕生日・年齢をメンバとして持つクラスで、CompareTo()メソッドでは年齢によって比較を行います。
+
それでは、ソートを行う上で必要とされる&msdn(netfx,type,System.IComparable){IComparableインターフェイス};(System名前空間)を実装するためにIComparableについて詳しく見ていきます。 IComparableインターフェイスを実装する場合は一つのメソッド&msdn(netfx,method,System.IComparable.CompareTo){CompareTo};を実装しなければなりません。 このメソッドでは、一つのobject型の引数を取り、インターフェイスを実装するインスタンスと引数のオブジェクトとの大小関係に応じて次の値を返す必要があります。
 

        

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

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

          
+
#tabpage(C#)
+
#code(cs){{
+
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);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
 
Imports System.Collections
Imports System.Collections
 

        

        
~
Class Account
Public Class Girl
~
  Implements IComparable
    ' IComparableインターフェイスをインプリメント
-
    Implements IComparable
 

        

        
~
  Dim id As Integer
    ' フィールド
~
  Dim name As String
    Public Name As String
-
    Public Age As Integer
-
    Public BirthDate As DateTime
 

        

        
~
  Public Sub New(ByVal id As Integer, ByVal name As String)
    ' コンストラクタ
~
    MyClass.id = id
    Public Sub New()
~
    MyClass.name = name
    End Sub
+
  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
    Public Sub New(ByVal Name As String, ByVal BirthDate As DateTime)
+
  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
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        MyClass.Name = Name
~
[before sort]
        MyClass.BirthDate = BirthDate
+
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フィールドの値にしたがってソート出来ていることが分かると思います。
        ' 年齢を計算
-
        MyClass.Age = DateTime.Now.Year - BirthDate.Year
 

        

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

        

        
~
また、null(Nothing)との比較についても、上記のコードではArgumentNullExceptionをスローするようにしていますが、string型のようにnullはすべてのオブジェクトよりも小さいと定義することもできます。
        If birthDayInThisYear > DateTime.Now Then Me.Age -= 1
 

        

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

        

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

        

        
~
IComparerインターフェイスを実装する場合は一つのメソッド&msdn(netfx,method,System.Collections.IComparer.Compare){Compare};を実装しなければなりません。 IComparable.CompareToメソッドが一つのobject型の引数を取るのに対し、IComparer.Compareメソッドでは二つのobject型の引数を取ります。 戻り値は、IComparable.CompareToメソッドと同様、引数同士の大小関係に応じて次の値を返す必要があります。
        Return Name + "(" + BirthDate.ToLongDateString() + "生まれ, " + Age.ToString() + "歳)"
 

        

        
~
|*IComparer.Compareの戻り値
    End Function
+
|~引数xとyの大小関係|~戻り値として返す値|h
+
|並べ替えたときに、引数xが引数yよりも''前''となる&br;(xはyよりも'''小さい''')|0より小さい値|
+
|並べ替えたときに、引数xが引数yと''同じ''位置となる&br;(xはyと'''等しい''')|0|
+
|並べ替えたときに、引数xが引数yよりも''後''となる&br;(xはyよりも'''大きい''')|0より大きい値|
 

        

        
~
早速、上記の仕様を満たすようIComparerを実装したクラスを用意し、ソートしてみます。 次のコードでは、IComparerを実装してint型の配列を昇順・降順で並べ替えるためのクラスを作成し、ソートに使っています。
    ' IComparable.CompareTo()メソッドを実装
-
    Public Function CompareTo(ByVal x As Object) As Integer Implements IComparable.CompareTo
 

        

        
~
#tabpage(C#)
        ' 渡されたオブジェクトの型を確認
~
#code(cs){{
        If TypeOf x Is Girl Then
+
using System;
+
using System.Collections;
 

        

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

        

        
~
    return ix - iy;
            If Me.Age > g.Age Then
+
    // 次のように記述することもできる
+
    //return ix.CompareTo(iy);
+
  }
+
}
 

        

        
~
// intを降順で並べ替えるIComparerの実装
                ' このインスタンスのAgeが渡されたインスタンスのAgeより大きいとき
~
class DescendingOrderComparer : IComparer
                Return 1
+
{
+
  public int Compare(object x, object y)
+
  {
+
    int ix = (int)x;
+
    int iy = (int)y;
 

        

        
~
    return iy - ix;
            ElseIf Me.Age = g.Age Then
+
    // 次のように記述することもできる
+
    //return iy.CompareTo(ix);
+
    //return -ix.CompareTo(iy);
+
  }
+
}
 

        

        
~
class Sample
                ' このインスタンスのAgeが渡されたインスタンスのAgeと同じとき
~
{
                Return 0
+
  static void Main()
+
  {
+
    int[] arr = new int[] {3, 4, 2, 0, 1};
 

        

        
~
    Console.WriteLine("[before sort]");
            ElseIf Me.Age < g.Age Then
 

        

        
~
    Print(arr);
                ' このインスタンスのAgeが渡されたインスタンスのAgeより小さいとき
-
                Return -1
 

        

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

        

        
~
    Array.Sort(arr, new AscendingOrderComparer());
        End If
 

        

        
~
    Print(arr);
        ' 比較できないとき
-
        Return 0
 

        

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

        

        
~
    Array.Sort(arr, new DescendingOrderComparer());
End Class
~

          
}}
+
    Print(arr);
+

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

          
+
    Array.Sort(arr);
+

          
+
    Print(arr);
+

          
+
    Array.Reverse(arr);
 

        

        
~
    Print(arr);
CompareTo()メソッドでは引数の型がObject型なので一度渡されたオブジェクトの型を確認してから、自分のクラス型であるGirl型にキャストします。 その後渡されたインスタンスと自分自身を比較して適切な値を返してやります。 ArrayListにGirl型以外のインスタンスが格納されていた場合など、比較できない型が渡された場合に備え、そのような場合には等価であると定義しておきます。 実際にこのクラスを用いた例が次のコードです。
+
  }
 

        

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

          
+
    Console.WriteLine();
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
 
Imports System.Collections
Imports System.Collections
 

        

        
~
' Integerを昇順で並べ替えるIComparerの実装
Public Class Comparison
+
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
    Public Shared Function Main(ByVal args() As String) As Integer
+
  Shared Sub Main()
+
    Dim arr() As Integer = New Integer() {3, 4, 2, 0, 1}
 

        

        
~
    Console.WriteLine("[before sort]")
        Dim arr As New ArrayList()
-
        arr.Add(New Girl("後藤真希", New DateTime(1985, 9, 23)))
-
        arr.Add(New Girl("加護亜依", New DateTime(1988, 2, 7)))
-
        arr.Add(New Girl("辻希美", New DateTime(1987, 6, 17)))
-
        arr.Add(New Girl("松浦亜弥", New DateTime(1986, 6, 25)))
-
        arr.Add(New Girl("紺野あさ美", New DateTime(1987, 5, 7)))
-
        arr.Add(New Girl("藤本美貴", New DateTime(1985, 2, 26)))
 

        

        
~
    Print(arr)
        Dim g As Girl
 

        

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

        

        
~
    Array.Sort(arr, New AscendingOrderComparer())
        For Each g In arr
 

        

        
~
    Print(arr)
            Console.WriteLine(g)
 

        

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

        

        
~
    Array.Sort(arr, New DescendingOrderComparer())
        arr.Sort()
 

        

        
~
    Print(arr)
        Console.WriteLine("    After sorting...")
 

        

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

        

        
~
    Array.Sort(arr)
            Console.WriteLine(g)
 

        

        
~
    Print(arr)
        Next
 

        

        
~
    Array.Reverse(arr)
        Return 0
 

        

        
~
    Print(arr)
    End Function
+
  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
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
[before sort]
       Before sorting...
~
3, 4, 2, 0, 1, 
後藤真希(1985年9月23日生まれ, 18歳)
~
[AscendingOrderComparer]
加護亜依(1988年2月7日生まれ, 15歳)
~
0, 1, 2, 3, 4, 
辻希美(1987年6月17日生まれ, 16歳)
~
[DescendingOrderComparer]
松浦亜弥(1986年6月25日生まれ, 17歳)
~
4, 3, 2, 1, 0, 
紺野あさ美(1987年5月7日生まれ, 16歳)
~
[Sort + Reverse]
藤本美貴(1985年2月26日生まれ, 18歳)
~
0, 1, 2, 3, 4, 
    After sorting...
~
4, 3, 2, 1, 0, 
加護亜依(1988年2月7日生まれ, 15歳)
-
紺野あさ美(1987年5月7日生まれ, 16歳)
-
辻希美(1987年6月17日生まれ, 16歳)
-
松浦亜弥(1986年6月25日生まれ, 17歳)
-
藤本美貴(1985年2月26日生まれ, 18歳)
-
後藤真希(1985年9月23日生まれ, 18歳)
-
Press any key to continue
 
}}
}}
 

        

        
~
Array.Sortメソッドでは引数にIComparerを指定すると、そのIComparerを使って並べ替えが行われるようになります。 その結果、上記のように昇順・降順でのソートが行えるようになります。 なお、上記のコードでは、比較のためにIComparerを使わずArray.SortメソッドとArray.Reverseメソッドを使って降順にソートする例も記述しています。
この結果を見るとわかるとおり、CompareTo()では年齢という数値だけを比較しているので、誕生日の順番は考慮されていません。 誕生日で比較させる場合にはCompareTo()メソッド内における比較処理を誕生日で行うように変えてやります。 その場合のソースコードと実行結果は次のようになります。
 

        

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

          
+
#tabpage(C#)
+
#code(cs){{
+
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);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
 
Imports System.Collections
Imports System.Collections
 

        

        
~
Class Account
Public Class Girl
~
  Private _id As Integer
    ' IComparableインターフェイスをインプリメント
~
  Private _name As String
    Implements IComparable
+

          
+
  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()
    ' IComparable.CompareTo()メソッドを実装
-
    Public Function CompareTo(ByVal x As Object) As Integer Implements IComparable.CompareTo
 

        

        
~
    Print(arr)
        ' 渡されたオブジェクトの型を確認
-
        If TypeOf x Is Girl Then
 

        

        
~
    Console.WriteLine("[{0} {1}]", comparer.Mode, comparer.Order)
            Dim g As Girl = CType(x, Girl)
 

        

        
~
    Array.Sort(arr, comparer)
            If Me.BirthDate > g.BirthDate Then
 

        

        
~
    Print(arr)
                ' このインスタンスのBirthDateが渡されたインスタンスのBirthDateより大きいとき
-
                Return 1
 

        

        
~
    comparer.Mode = AccountComparer.SortMode.ByName
            ElseIf Me.BirthDate = g.BirthDate Then
 

        

        
~
    Console.WriteLine("[{0} {1}]", comparer.Mode, comparer.Order)
                ' このインスタンスのBirthDateが渡されたインスタンスのBirthDateと同じとき
-
                Return 0
 

        

        
~
    Array.Sort(arr, comparer)
            ElseIf Me.BirthDate < g.BirthDate Then
 

        

        
~
    Print(arr)
                ' このインスタンスのBirthDateが渡されたインスタンスのBirthDateより小さいとき
-
                Return -1
 

        

        
~
    comparer.Order = AccountComparer.SortOrder.Descending
            End If
 

        

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

        

        
~
    Array.Sort(arr, comparer)
        ' 比較できないとき
-
        Return 0
 

        

        
~
    Print(arr)
    End Function
+
  End Sub
 

        

        
+
  Shared Sub Print(ByVal c As IEnumerable)
+
    For Each e As Account In c
+
      Console.WriteLine(e)
+
    Next
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
1:Eve
    Before sorting...
~
4:Dave
後藤真希(1985年9月23日生まれ, 18歳)
~
2:Alice
加護亜依(1988年2月7日生まれ, 15歳)
~
0:Charlie
辻希美(1987年6月17日生まれ, 16歳)
~
3:Bob
松浦亜弥(1986年6月25日生まれ, 17歳)
~
[ByID Ascending]
紺野あさ美(1987年5月7日生まれ, 16歳)
~
0:Charlie
藤本美貴(1985年2月26日生まれ, 18歳)
~
1:Eve
    After sorting...
~
2:Alice
藤本美貴(1985年2月26日生まれ, 18歳)
~
3:Bob
後藤真希(1985年9月23日生まれ, 18歳)
~
4:Dave
松浦亜弥(1986年6月25日生まれ, 17歳)
~
[ByName Ascending]
紺野あさ美(1987年5月7日生まれ, 16歳)
~
2:Alice
辻希美(1987年6月17日生まれ, 16歳)
~
3:Bob
加護亜依(1988年2月7日生まれ, 15歳)
~
0:Charlie
Press any key to continue
+
4:Dave
+
1:Eve
+
[ByName Descending]
+
1:Eve
+
4:Dave
+
0:Charlie
+
3:Bob
+
2:Alice
 
}}
}}
 

        

        
~
この例を見てわかるとおり、比較される側であるAccountクラスでは、必ずしもIComparerを実装している必要はありません。 また、IComparerを実装する側では、比較対象に合わせて比較処理を定義することができます。
*IComparerを使ったソート
-
IComparableインターフェイスをクラスに実装すれば比較を行えるようになるのですが、先ほどの例のようにソート方法を変えたい場合はコードを書き換えなければなりません。 ソート方法が誕生日だけでいい場合は問題ありませんが、年齢順や名前順でソートしたくなる場合が出てきます。 そのような場合はIComparableインターフェイスではなくIComaparerインターフェイスを用います。
-

          
-
このインターフェイスは比較を行いたいクラスに実装するのではなく、比較を行わせるクラスに実装します。 つまり、比較対照と比較方法を区別することができるのです。 IComparableの時と同様に、IComparerを実装する場合はCompare()メソッドを実装する必要があります。 その詳細は次のようなものです。
 

        

        
~
**IComparable<T>
+二つのObject型を引数にとり、Integer型の値を返す。 つまりシグニチャは「Function CompareTo( ByVal x As Object, ByVal y As Object ) As Integer」
~
&msdn(netfx,id,4d7sx9hd){IComparable<T>インターフェイス};(System名前空間)はIComparableインターフェイスのジェネリック版で、型パラメータTで指定された型との比較が可能であることを表すインターフェイスです。 IComparableと同様CompareToメソッドを実装する必要がありますが、引数の型はobjectではなく型パラメータで指定された型となるため、実装に際して型のチェックをする必要が無くなります。
+xとyを比較し、次の値を関数の戻り値として返す。
-
++xよりyが大きいと比較できるときは 0より大きい値
-
++xとyが同じと比較できるときは 0
-
++xよりyが小さいと比較できるときは 0より小さい値
 

        

        
~
以下の例は、IComparableでの例をIComparable<T>インターフェイス使ったものに書き換えたものです。
Compare()メソッドはCompareTo()とは異なり二つのオブジェクトを引数にとります。 CompareTo()メソッドは自分自身とを比較したのに対し、Compare()メソッドでは自分自身は比較方法を定義するクラスで、比較対照ではないからです。 後の戻り値は CompareTo()と大差ありません。 これを実装したクラスの例を示します。
 

        

        
+
#tabpage(C#)
+
#code(cs){{
+
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);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
 
Imports System
Imports System
~
Imports System.Collections.Generic
Imports System.Collections
 

        

        
~
Class Account
' 比較方法を設定する列挙型
~
  Implements IComparable(Of Account)
Public Enum GirlComparisonMode As Integer
 

        

        
~
  Dim id As Integer
    ByName = 0
~
  Dim name As String
    ByBirthDate = 1
-
    ByAge = 2
 

        

        
~
  Public Sub New(ByVal id As Integer, ByVal name As String)
End Enum
+
    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
' Girlクラスの比較を行うクラス
~
  Shared Sub Main()
Public Class GirlComparer
~
    Dim arr() As Account = New Account() { _
    Implements IComparer
+
      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
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
    ' 比較方法
~
[before sort]
    Public ComparisonMode As GirlComparisonMode
+
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>を実装することも出来ます。
    ' コンストラクタ
-
    Public Sub New()
-
        ComparisonMode = GirlComparisonMode.ByName
-
    End Sub
 

        

        
~
#tabpage(C#)
    ' 比較メソッド
~
#code(cs){{
    Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
+
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との比較処理
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class Account
        ' オブジェクトの型を確認
~
  Implements IComparable(Of Account)
        If TypeOf x Is Girl AndAlso TypeOf y Is Girl Then
+
  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
+
}}
+
#tabpage-end
 

        

        
-
            Dim gx As Girl = CType(x, Girl)
-
            Dim gy As Girl = CType(y, Girl)
 

        

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

        

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

        

        
~
#tabpage(C#)
                    ' 名前による比較
~
#code(cs){{
                    If gx.Name > gy.Name Then
+
using System;
+
using System.Collections.Generic;
 

        

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

        

        
~
// 降順で並べ替えるIComparer<int>の実装
                    ElseIf gx.Name = gy.Name Then
+
class DescendingOrderComparer : IComparer<int>
+
{
+
  public int Compare(int x, int y)
+
  {
+
    return y - x;
+
  }
+
}
 

        

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

        

        
~
    Console.WriteLine("[before sort]");
                    ElseIf gx.Name < gy.Name Then
 

        

        
~
    Print(arr);
                        Return -1
 

        

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

        

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

        

        
~
    Print(arr);
                Case GirlComparisonMode.ByBirthDate
 

        

        
~
    // DescendingOrderComparerを使ってソート
                    ' 誕生日による比較
~
    Console.WriteLine("[DescendingOrderComparer]");
                    If gx.BirthDate > gy.BirthDate Then
 

        

        
~
    Array.Sort(arr, new DescendingOrderComparer());
                        Return 1
 

        

        
~
    Print(arr);
                    ElseIf gx.BirthDate = gy.BirthDate Then
+
  }
 

        

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

        

        
~
    Console.WriteLine();
                    ElseIf gx.BirthDate < gy.BirthDate Then
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections.Generic
 

        

        
~
' 昇順で並べ替えるIComparer(Of Integer)の実装
                        Return -1
+
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)の実装
                    End If
+
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]")
                Case GirlComparisonMode.ByAge
 

        

        
~
    Print(arr)
                    ' 年齢による比較
-
                    Return gx.Age - gy.Age
 

        

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

        

        
~
    Array.Sort(arr, New AscendingOrderComparer())
        End If
 

        

        
~
    Print(arr)
        ' 比較できない場合
-
        Return 0
 

        

        
~
    ' DescendingOrderComparerを使ってソート
    End Function
+
    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
End Class
+
}}
+
#tabpage-end
 

        

        
+
#prompt{{
+
[before sort]
+
3, 4, 2, 0, 1, 
+
[AscendingOrderComparer]
+
0, 1, 2, 3, 4, 
+
[DescendingOrderComparer]
+
4, 3, 2, 1, 0, 
+
}}
 

        

        
~
**Comparision<T>
' Girlクラス
~
&msdn(netfx,id,tfakywbh){Comparison<T>デリゲート};(System名前空間)は、二つのオブジェクトを比較するメソッド、つまりIComparer<T>.Compareメソッドのような比較処理を行うメソッドを表すデリゲートです。 Array.SortおよびList<T>.Sortメソッドはこのデリゲートを引数に取ることができ、ソートの際の比較処理に使用することが出来ます。
Public Class Girl
 

        

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

        

        
~
#tabpage(C#)
    ' コンストラクタ
~
#code(cs){{
    Public Sub New()
~
using System;
    End Sub
+
using System.Collections.Generic;
 

        

        
~
class Sample
    Public Sub New(ByVal Name As String, ByVal BirthDate As DateTime)
+
{
+
  // int型の値を降順で比較するメソッド
+
  static int CompareDescendingOrder(int x, int y)
+
  {
+
    return y - x;
+
  }
 

        

        
~
  static void Main()
        MyClass.Name = Name
~
  {
        MyClass.BirthDate = BirthDate
+
    int[] arr = new int[] {3, 4, 2, 0, 1};
 

        

        
~
    Console.WriteLine("[before sort]");
        ' 年齢を計算
-
        MyClass.Age = DateTime.Now.Year - BirthDate.Year
 

        

        
~
    Print(arr);
        Dim birthDayInThisYear As New DateTime(DateTime.Now.Year, BirthDate.Month, BirthDate.Day)
 

        

        
~
    // Comparison<int>を指定せずにソート
        If birthDayInThisYear > DateTime.Now Then Me.Age -= 1
+
    Console.WriteLine("[Sort]");
 

        

        
~
    Array.Sort(arr);
    End Sub
 

        

        
~
    Print(arr);
    ' ToString()メソッドをオーバーライド
-
    Public Overrides Function ToString() As String
 

        

        
~
    // Comparison<int>を指定してソート
        Return Name + "(" + BirthDate.ToLongDateString() + "生まれ, " + Age.ToString() + "歳)"
+
    Comparison<int> comparison = CompareDescendingOrder;
 

        

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

        

        
~
    Array.Sort(arr, comparison);
    ' CompareTo()メソッドは必要ない
 

        

        
~
    // 単に次のように記述することも出来る
End Class
+
    //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();
Public Class Comparison
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections.Generic
 

        

        
~
Class Sample
    Public Shared Function Main(ByVal args() As String) As Integer
+
  ' 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 New ArrayList()
~
    Dim arr() As Integer = New Integer() {3, 4, 2, 0, 1}
        arr.Add(New Girl("後藤真希", New DateTime(1985, 9, 23)))
-
        arr.Add(New Girl("加護亜依", New DateTime(1988, 2, 7)))
-
        arr.Add(New Girl("辻希美", New DateTime(1987, 6, 17)))
-
        arr.Add(New Girl("松浦亜弥", New DateTime(1986, 6, 25)))
-
        arr.Add(New Girl("紺野あさ美", New DateTime(1987, 5, 7)))
-
        arr.Add(New Girl("藤本美貴", New DateTime(1985, 2, 26)))
 

        

        
~
    Console.WriteLine("[before sort]")
        Dim g As Girl
 

        

        
~
    Print(arr)
        Console.WriteLine("    Before sorting...")
 

        

        
~
    ' Comparison(Of Integer)を指定せずにソート
        For Each g In arr
+
    Console.WriteLine("[Sort]")
 

        

        
~
    Array.Sort(arr)
            Console.WriteLine(g)
 

        

        
~
    Print(arr)
        Next
 

        

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

        

        
~
    Console.WriteLine("[Sort + Comparison(Of Integer)]")
        For i = 0 To 2
 

        

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

        

        
~
    Print(arr)
            arr.Sort(c)
+
  End Sub
 

        

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

        

        
~
    Console.WriteLine()
            For Each g In arr
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
                Console.WriteLine(g)
+
[before sort]
+
3, 4, 2, 0, 1, 
+
[Sort]
+
0, 1, 2, 3, 4, 
+
[Sort + Comparison(Of Integer)]
+
4, 3, 2, 1, 0, 
+
}}
 

        

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

        

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

        

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

        

        
~
#tabpage(C#)
    End Function
+
#code(cs){{
+
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();
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
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
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#prompt(実行結果){{
~
[before sort]
    Before sorting...
~
3, , 4, 2, 0, 1, 
後藤真希(1985年9月23日生まれ, 18歳)
~
[after sort]
加護亜依(1988年2月7日生まれ, 15歳)
~
, 4, 3, 2, 1, 0, 
辻希美(1987年6月17日生まれ, 16歳)
-
松浦亜弥(1986年6月25日生まれ, 17歳)
-
紺野あさ美(1987年5月7日生まれ, 16歳)
-
藤本美貴(1985年2月26日生まれ, 18歳)
-
    Sorted ByName
-
加護亜依(1988年2月7日生まれ, 15歳)
-
後藤真希(1985年9月23日生まれ, 18歳)
-
松浦亜弥(1986年6月25日生まれ, 17歳)
-
紺野あさ美(1987年5月7日生まれ, 16歳)
-
藤本美貴(1985年2月26日生まれ, 18歳)
-
辻希美(1987年6月17日生まれ, 16歳)
-
    Sorted ByBirthDate
-
藤本美貴(1985年2月26日生まれ, 18歳)
-
後藤真希(1985年9月23日生まれ, 18歳)
-
松浦亜弥(1986年6月25日生まれ, 17歳)
-
紺野あさ美(1987年5月7日生まれ, 16歳)
-
辻希美(1987年6月17日生まれ, 16歳)
-
加護亜依(1988年2月7日生まれ, 15歳)
-
    Sorted ByAge
-
加護亜依(1988年2月7日生まれ, 15歳)
-
紺野あさ美(1987年5月7日生まれ, 16歳)
-
辻希美(1987年6月17日生まれ, 16歳)
-
松浦亜弥(1986年6月25日生まれ, 17歳)
-
藤本美貴(1985年2月26日生まれ, 18歳)
-
後藤真希(1985年9月23日生まれ, 18歳)
-
Press any key to continue
 
}}
}}
 

        

        
~
なお、Comparer<T>クラスで実装されているIComparer.Compareの比較処理では、null(Nothing)はnull以外のどのような値よりも小さいとみなされます。 この動作を変えたい場合は、派生クラスでIComparer.Compareを再実装する必要があります。
実際に比較方法を指定するためには、IComparerを実装したクラスのインスタンスを作成し、Sort()メソッドを呼び出すときにそのインスタンスを引数として渡します。 この例ではForステートメントにより定義した3種類の比較方法を使用してSort()メソッドを呼び出し、その結果を表示しています。
+

          
+
**StringComparer
+
&msdn(netfx,type,System.StringComparer){StringComparerクラス};(System名前空間)は、文字列比較に特化したIComparerの実装です。 大文字小文字を無視した比較、現在のカルチャ・インバリアントカルチャの規則に基づいた比較などを行う実装が用意されています。 詳細については、[[StringComparerについての個別の解説>programming/netfx2/overview/stringoperation#StringComparisonStringComparer]]を参照してください。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+

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

          
+
    Print(arr);
+

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

        

        
~
    Array.Sort(arr, StringComparer.CurrentCulture);
この例のように比較方法を外部クラスで指定する場合は、比較対照となるクラスはIComparableを実装している必要はありません。 このソースコードでもGirlクラスはIComparableを実装していません。 それに変わって、IComparerを実装したクラスでは適切な比較方法を定義していなければなりません。
 

        

        
~
    Print(arr);
*ListViewコントロールでIComparerを使う
-
ListViewコントロールはその特性上、最もよくソートを行うと思います。 また、ソート方法も場合によって様々です。 ListViewコントロールで独自のソート方法を定義するためにIComparerを適用したサンプルを作ってみました。 コメントをふってあるので詳しい説明はしませんが、いくつか応用的なコードもあるのでぜひ参考にして下さい。
 

        

        
+
    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();
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
~
Imports System
' 比較方法を指定する列挙型
-
Public Enum ComparisonMode
 

        

        
~
Class Sample
    ByName = 0
~
  Shared Sub Main()
    ByBirthDate = 1
~
    Dim arr() As String = New String() {"亜", "絵", "井", "尾", "宇"}
    ByAge = 2
-
    ByBloodType = 3
 

        

        
~
    Print(arr)
End Enum
 

        

        
~
    Console.WriteLine("[StringComparer.CurrentCulture]")
' 比較を行うクラス
-
Public Class Comparer
-
    ' IComparerインターフェイスをインプリメント
-
    Implements IComparer
 

        

        
~
    Array.Sort(arr, StringComparer.CurrentCulture)
    ' 比較方法
-
    Public ComparisonMode As ComparisonMode
 

        

        
~
    Print(arr)
    ' 昇順か降順か ( System.Windows.Forms.SortOrder列挙型を流用 )
-
    Public SortOrder As SortOrder
 

        

        
~
    Console.WriteLine("[StringComparer.InvariantCulture]")
    ' コンストラクタ
-
    Public Sub New()
-
    End Sub
 

        

        
~
    Array.Sort(arr, StringComparer.InvariantCulture)
    Public Sub New(ByVal comparisonMode As ComparisonMode, ByVal sortOrder As SortOrder)
 

        

        
~
    Print(arr)
        Me.ComparisonMode = comparisonMode
~
  End Sub
        Me.SortOrder = sortOrder
 

        

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

        

        
~
    Console.WriteLine()
    ' 比較を行うメソッド
~
  End Sub
    Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        ' 規定の戻り値
~
亜, 絵, 井, 尾, 宇, 
        Dim intCompareResult As Integer = 0
+
[StringComparer.CurrentCulture]
+
亜, 井, 宇, 絵, 尾, 
+
[StringComparer.InvariantCulture]
+
井, 亜, 宇, 尾, 絵, 
+
}}
 

        

        
~
----
        ' ソート無しの時
-
        If SortOrder = SortOrder.None Then Return intCompareResult
 

        

        
~
*等価性の比較
        ' 比較対象がListViewItemの時 ( この型で渡される )
~
**Object.Equals
        If TypeOf x Is ListViewItem AndAlso TypeOf y Is ListViewItem Then
+
まずは、&msdn(netfx,method,System.Object.Equals){Object.Equals};メソッドとオブジェクトの等価性について見ていきます。 Equalsメソッドは現在のインスタンスと引数で指定されたオブジェクトとの等価性を比較するメソッドですが、Equalsメソッドをオーバーライドしない場合のデフォルトの動作は次のようになっています。
+

          
+
:値型(struct)の場合|二つの値型オブジェクトがビット単位で等しい場合にtrue、そうでなければfalseを返す
+
:参照型(class)の場合|二つの参照型オブジェクトが同一のオブジェクト(=参照先のオブジェクトが同一)である場合にtrue、そうでなければfalseを返す
+

          
+
そのため、次の例ではAccountクラスはEqualsメソッドをオーバーライドしていないため、各フィールドが同じ値を持つインスタンスでも異なるインスタンスの場合である限り等しいとは判断されません。 これにより、Array.IndexOfメソッドによる検索でも配列内には同一のインスタンスは存在しないため-1が返されます。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+

          
+
class Account
+
{
+
  int id;
+
  string 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 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"),
+
    };
+

          
+
    Account charlie = new Account(0, "Charlie");
+

          
+
    Console.WriteLine("'{0}' Equals '{1}': {2}", charlie, arr[3], charlie.Equals(arr[3]));
+
    Console.WriteLine("IndexOf '{0}': {1}", charlie, Array.IndexOf(arr, charlie));
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class Account
            ' ListItem型へキャスト
~
  Dim id As Integer
            Dim itemX As ListViewItem = CType(x, ListViewItem)
~
  Dim name As String
            Dim itemY As ListViewItem = CType(y, ListViewItem)
+

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

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

        

        
~
Class Sample
            ' 比較方法とアイテムから比較すべき文字列を取得
~
  Shared Sub Main()
            Dim textX As String = itemX.SubItems(ComparisonMode).Text
~
    Dim arr() As Account = New Account() { _
            Dim textY As String = itemY.SubItems(ComparisonMode).Text
+
      New Account(1, "Eve"), _
+
      New Account(4, "Dave"), _
+
      New Account(2, "Alice"), _
+
      New Account(0, "Charlie"), _
+
      New Account(3, "Bob") _
+
    }
+

          
+
    Dim charlie As New Account(0, "Charlie")
+

          
+
    Console.WriteLine("'{0}' Equals '{1}': {2}", charlie, arr(3), charlie.Equals(arr(3)))
+
    Console.WriteLine("IndexOf '{0}': {1}", charlie, Array.IndexOf(arr, charlie))
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
            ' 比較方法別の処理
~
'0:Charlie' Equals '0:Charlie': False
            Select Case ComparisonMode
+
IndexOf '0:Charlie': -1
+
}}
 

        

        
~
Equalsメソッドをオーバーライドすることで、どのような場合にオブジェクトが等しいとするかを定義することが出来ます。 次の例は、上記の例を書き換えEqualsメソッドをオーバーライドするようにしたものです。
                Case ComparisonMode.ByAge
 

        

        
~
#tabpage(C#)
                    ' 年齢による比較
~
#code(cs){{
                    Dim ageX As Integer = Integer.Parse(textX)
~
using System;
                    Dim ageY As Integer = Integer.Parse(textY)
+

          
+
class Account
+
{
+
  int id;
+
  string name;
+

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

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

          
+
  public override bool Equals(object obj)
+
  {
+
    // Accountクラスに型変換
+
    Account other = obj as Account;
+

          
+
    if (other == null)
+
    {
+
      // objがAccountクラスでない場合、またはnullの場合は等しくないとする
+
      return false;
+
    }
+
    else
+
    {
+
      // objがAccountクラスの場合、かつフィールドidとnameの値が等しい場合は、
+
      // 2つのオブジェクトが等しいとする
+
      return this.id == other.id && this.name == other.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"),
+
    };
+

          
+
    Account charlie = new Account(0, "Charlie");
+

          
+
    Console.WriteLine("'{0}' Equals '{1}': {2}", charlie, arr[3], charlie.Equals(arr[3]));
+
    Console.WriteLine("IndexOf '{0}': {1}", charlie, Array.IndexOf(arr, charlie));
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class Account
                    ' その差を結果とする
~
  Dim id As Integer
                    intCompareResult = ageX - ageY
+
  Dim name As String
+

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

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

          
+
  Public Overrides Function Equals(ByVal obj As Object) As Boolean
+
    ' Accountクラスに型変換
+
    Dim other As Account = TryCast(obj, Account)
+

          
+
    If other Is Nothing Then
+
      ' objがAccountクラスでない場合、またはNothingの場合は等しくないとする
+
      Return False
+
    Else
+
      ' objがAccountクラスの場合、かつフィールドidとnameの値が等しい場合は、
+
      ' 2つのオブジェクトが等しいとする
+
      Return MyClass.id = other.id AndAlso MyClass.name = other.name
+
    End If
+
  End Function
+
End Class
 

        

        
~
Class Sample
                Case ComparisonMode.ByBirthDate
+
  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 charlie As New Account(0, "Charlie")
+

          
+
    Console.WriteLine("'{0}' Equals '{1}': {2}", charlie, arr(3), charlie.Equals(arr(3)))
+
    Console.WriteLine("IndexOf '{0}': {1}", charlie, Array.IndexOf(arr, charlie))
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
                    ' 生年月日による比較
~
'0:Charlie' Equals '0:Charlie': True
                    Dim dtmX As DateTime = DateTime.Parse(textX)
~
IndexOf '0:Charlie': 3
                    Dim dtmY As DateTime = DateTime.Parse(textY)
+
}}
 

        

        
~
結果を見て分かる通り、二つの異なるインスタンスでも各フィールドの値が等しければ、二つのインスタンスは等しいと扱われるようになりました。 また、これにより、Array.IndexOfメソッドによる検索でも配列内にある''等しい''インスタンスのインデックスが返されるようになりました。
                    If dtmX > dtmY Then
 

        

        
~
なお、Equalsメソッドをオーバーライドする場合は&msdn(netfx,method,System.Object.GetHashCode){Object.GetHashCodeメソッド};もオーバーライドする必要があります。 そのため、上記の例をコンパイルすると警告(&msdn(netfx,id,xxhbfytk){CS0659};)が出ます。 GetHashCodeが返す値は値型では特に重要になりますが、ここでは実装を省略しています。 詳しくはGetHashCodeメソッドの解説を参照してください。
                        intCompareResult = 1
 

        

        
~
**IEquatable<T>
                    ElseIf dtmX = dtmY Then
+
&msdn(netfx,id,ms131187){IEquatable<T>インターフェイス};(System名前空間)は、型パラメータTで指定された型との等価性の比較が可能であることを表すインターフェイスです。 Object.Equalsメソッドがobject型の引数を取るのに対し、&msdn(netfx,id,ms131190){IEquatable<T>.Equalsメソッド};では型Tを引数に取ります。 このため、Object.Equalsメソッドをオーバーライドする場合とは異なり、型チェックの必要が無くなります。
 

        

        
~
以下の例は、先の例をIEquatable<T>インターフェイスを実装したものに書き換えたものです。
                        intCompareResult = 0
 

        

        
~
#tabpage(C#)
                    ElseIf dtmX < dtmY Then
+
#code(cs){{
+
using System;
+

          
+
class Account : IEquatable<Account>
+
{
+
  int id;
+
  string name;
+

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

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

          
+
  // IEquatable<Account>.Equalsの実装
+
  public bool Equals(Account other)
+
  {
+
    if (other == null) return false;
+

          
+
    return this.id == other.id && this.name == other.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"),
+
    };
+

          
+
    Account charlie = new Account(0, "Charlie");
+

          
+
    Console.WriteLine("'{0}' Equals '{1}': {2}", charlie, arr[3], charlie.Equals(arr[3]));
+
    Console.WriteLine("IndexOf '{0}': {1}", charlie, Array.IndexOf(arr, charlie));
+

          
+
    // objectにキャスト
+
    object obj = charlie;
+

          
+
    Console.WriteLine("'{0}' Equals '{1}': {2}", obj, arr[3], obj.Equals(arr[3]));
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+

          
+
Class Account
+
  Implements IEquatable(Of Account)
 

        

        
~
  Dim id As Integer
                        intCompareResult = -1
+
  Dim name As String
 

        

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

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

          
+
  ' IEquatable(Of Account).Equalsの実装
+
  Public Function Equals(ByVal other As Account) As Boolean Implements IEquatable(Of Account).Equals
+
    If other is Nothing Then Return False
+

          
+
    Return MyClass.id = other.id AndAlso MyClass.name = other.name
+
  End Function
+
End Class
 

        

        
~
Class Sample
                Case ComparisonMode.ByBloodType, ComparisonMode.ByName
+
  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 charlie As New Account(0, "Charlie")
 

        

        
~
    Console.WriteLine("'{0}' Equals '{1}': {2}", charlie, arr(3), charlie.Equals(arr(3)))
                    ' 名前・血液型による比較 ( String型のCompare()メソッドをを流用する )
~
    Console.WriteLine("IndexOf '{0}': {1}", charlie, Array.IndexOf(arr, charlie))
                    intCompareResult = String.Compare(textX, textY)
 

        

        
~
    ' Objectにキャスト
            End Select
+
    Dim obj As Object = charlie
 

        

        
~
    Console.WriteLine("'{0}' Equals '{1}': {2}", obj, arr(3), obj.Equals(arr(3)))
            ' 昇順降順の設定による戻り値の符号反転
~
  End Sub
            If SortOrder = SortOrder.Descending Then intCompareResult = -intCompareResult
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        End If
+
'0:Charlie' Equals '0:Charlie': True
+
IndexOf '0:Charlie': -1
+
'0:Charlie' Equals '0:Charlie': False
+
}}
 

        

        
~
単純にAccount.Equalsメソッドを呼び出した比較は正しく動作していますが、結果を見て分かる通りArray.IndexOfメソッドの結果は意図に反して-1を返しています。 これは、Array.IndexOfメソッドではオブジェクトがIEquatable<T>.Equalsメソッドを実装しているかどうかに関わらず、常にObject.Equalsメソッドを使って等価性の比較を行うためです。 AccountクラスはObject.Equalsをオーバーライドしていないため、このような結果となります。 objectにキャストしてからEqualsメソッドで比較した場合も同様に、意図に反してfalseを返しています。
        ' 戻り値
-
        Return intCompareResult
 

        

        
~
このように、型がIEquatable<T>を実装していても、比較する側がIEquatable<T>.Equalsメソッドを呼び出すようになっていない限り、意図しない結果となります。 そのため、多くの場合はIEquatable<T>を実装すると同時に、Object.Equalsメソッドもオーバーライドすることになります。 以下の例は、上記の例にObject.Equalsメソッドのオーバーライドを追加したものです。
    End Function
 

        

        
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+

          
+
class Account : IEquatable<Account>
+
{
+
  int id;
+
  string name;
+

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

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

          
+
  // IEquatable<Account>.Equalsの実装
+
  public bool Equals(Account other)
+
  {
+
    if (other == null) return false;
+

          
+
    return this.id == other.id && this.name == other.name;
+
  }
+

          
+
  // Object.Equalsをオーバーライド
+
  public override bool Equals(object obj)
+
  {
+
    // Accountに型変換
+
    Account other = obj as Account;
+

          
+
    // 型変換出来ない場合、または引数がnullの場合はfalse
+
    if (other == null) return false;
+

          
+
    // 型変換出来た場合は、IEquatable<Account>.Equalsを使って比較する
+
    return Equals(other);
+
  }
+
}
+

          
+
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"),
+
    };
+

          
+
    Account charlie = new Account(0, "Charlie");
+

          
+
    Console.WriteLine("'{0}' Equals '{1}': {2}", charlie, arr[3], charlie.Equals(arr[3]));
+
    Console.WriteLine("IndexOf '{0}': {1}", charlie, Array.IndexOf(arr, charlie));
+

          
+
    // objectにキャスト
+
    object obj = charlie;
+

          
+
    Console.WriteLine("'{0}' Equals '{1}': {2}", obj, arr[3], obj.Equals(arr[3]));
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+

          
+
Class Account
+
  Implements IEquatable(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
+

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

          
+
  ' IEquatable(Of Account).Equalsの実装
+
  Public Function Equals(ByVal other As Account) As Boolean Implements IEquatable(Of Account).Equals
+
    If other is Nothing Then Return False
+

          
+
    Return MyClass.id = other.id AndAlso MyClass.name = other.name
+
  End Function
+

          
+
  ' Object.Equalsをオーバーライド
+
  Public Overrides Function Equals(ByVal obj As Object) As Boolean
+
    ' Accountに型変換
+
    Dim other As Account = TryCast(obj, Account)
+

          
+
    ' 型変換出来ない場合、または引数がNothingの場合はFalse
+
    If other Is Nothing Then Return False
+

          
+
    ' 型変換出来た場合は、IEquatable<Account>.Equalsを使って比較する
+
    Return Equals(other)
+
  End Function
 
End Class
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 charlie As New Account(0, "Charlie")
 

        

        
~
    Console.WriteLine("'{0}' Equals '{1}': {2}", charlie, arr(3), charlie.Equals(arr(3)))
' フォーム
~
    Console.WriteLine("IndexOf '{0}': {1}", charlie, Array.IndexOf(arr, charlie))
Public Class ComparisonDialog
-
    Inherits System.Windows.Forms.Form
 

        

        
~
    ' Objectにキャスト
#Region " Windows フォーム デザイナで生成されたコード "
+
    Dim obj As Object = charlie
 

        

        
~
    Console.WriteLine("'{0}' Equals '{1}': {2}", obj, arr(3), obj.Equals(arr(3)))
    Public Sub New()
~
  End Sub
        MyBase.New()
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        ' この呼び出しは Windows フォーム デザイナで必要です。
~
'0:Charlie' Equals '0:Charlie': True
        InitializeComponent()
+
IndexOf '0:Charlie': 3
+
'0:Charlie' Equals '0:Charlie': True
+
}}
+

          
+
**IEqualityComparer, IEqualityComparer<T>
+
&msdn(netfx,type,System.Collections.IEqualityComparer){IEqualityComparerインターフェイス};(System.Collections名前空間)と&msdn(netfx,id,ms132151){IEqualityComparer<T>インターフェイス};(System.Collections.Generic名前空間)は、等価性の比較処理を提供するためのインターフェイスです。 IEquatableとIEqualityComparerの関係はIComparableとIComparerの関係に似ていて、IEquatableではインターフェイスを実装する型に比較処理を実装するのに対し、IEqualityComparerでは比較される型とは別に比較処理を実装することが出来ます。
+

          
+
&msdn(netfx,type,System.Collections.Hashtable){Hashtable};はキーの比較にObject.Equals、&msdn(netfx,type,xfhwa508){Dictionary};はObject.EqualsまたはIEquatable<T>.Equalsをデフォルトで使用しますが、コンストラクタでキーの比較時に使用するIEqualityComparer・IEqualityComparer<T>を指定することが出来ます これにより、キーとなる型でこれらを実装・オーバーライドする代わりに指定したIEqualityComparer・IEqualityComparer<T>を用いてキーの比較処理を行わせるようにすることが出来ます。
+

          
+
インターフェイスの詳細と実装例は省略します。 次に解説するEqualityComparer<T>クラスを参照してください。
+

          
+
**EqualityComparer<T>
+
&msdn(netfx,id,ms132123){EqualityComparer<T>クラス};(System.Collections.Generic名前空間)は、IEqualityComparer非ジェネリックインターフェイスとIEqualityComparer<T>ジェネリックインターフェイスを実装する抽象クラスです。 Comparer<T>クラスと同様、IEqualityCompareとIEqualityComparer<T>の二つのインターフェイスを実装するクラスを作成しなくても、このクラスを継承して抽象メソッドである&msdn(netfx,id,ms132128){Equalsメソッド};と&msdn(netfx,id,ms132131){GetHashCodeメソッド};を実装するだけで同じ機能を提供することができるようになります。
+

          
+
EqualityComparer<T>.EqualsメソッドはT型の引数を二つ取り、等しい場合はtrue、そうでない場合はfalseを返すように実装します。 EqualityComparer<T>.GetHashCodeメソッドはT型の引数を一つ取り、引数で指定されたオブジェクトのハッシュ値を返すように実装します。 なお、二つのオブジェクトxとyについて、Equalsメソッドがtrueとなる場合はGetHashCodeメソッドがxとyに対して同じ値を返すように実装しなければなりません。
+

          
+
以下の例では、EqualityComparer<T>を継承したクラスを作成し、Dictionaryでのキーの比較に使用しています。 AccountクラスではIEquatable<T>やEqualsメソッドをオーバーライドしていない点に注目してください。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Collections.Generic;
+

          
+
class Account
+
{
+
  public int ID;
+
  public string Name;
+

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

          
+
  public override string ToString()
+
  {
+
    return string.Format("{0}:{1}", ID, Name);
+
  }
+
}
+

          
+
// AccountクラスのためのEqualityComparer
+
class AccountComparer : EqualityComparer<Account>
+
{
+
  public override bool Equals(Account x, Account y)
+
  {
+
    // xとyがともにnullの場合は等しいものとする
+
    if (x == null && y == null) return true;
+
    // xとyのどちらかがnullの場合は異なるものとする
+
    else if (x == null || y == null) return false;
+

          
+
    // フィールドIDが等しければ二つのAccountは等しいものとする
+
    return x.ID == y.ID;
+
  }
+

          
+
  public override int GetHashCode(Account obj)
+
  {
+
    // nullの場合はArgumentNullExceptionをスローする
+
    if (obj == null) throw new ArgumentNullException("obj");
+

          
+
    // ハッシュ値にフィールドIDの値を使用する
+
    return obj.ID;
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    // アカウントとコンタクト先のディクショナリ
+
    Dictionary<Account, Uri> dict = new Dictionary<Account, Uri>(new AccountComparer());
+

          
+
    Account alice   = new Account(2, "Alice");
+
    Account bob     = new Account(0, "Bob");
+
    Account charlie = new Account(1, "Charlie");
+

          
+
    dict[alice]   = new Uri("mailto:alice@example.net");
+
    dict[bob]     = new Uri("mailto:bob@example.net");
+
    dict[charlie] = new Uri("mailto:charlie@example.net");
+

          
+
    foreach (KeyValuePair<Account, Uri> pair in dict)
+
    {
+
      Console.WriteLine("{0,-10} => {1}", pair.Key, pair.Value);
+
    }
+

          
+
    Console.WriteLine();
+

          
+
    // IDフィールドはBobと同じ、NameフィールドはBobと異なるインスタンスdaveを作成
+
    Account dave = new Account(0, "Dave");
+

          
+
    // キーbob, daveに該当する要素が存在するかどうか
+
    Console.WriteLine("ContainsKey '{0}': {1}", bob,  dict.ContainsKey(bob));
+
    Console.WriteLine("ContainsKey '{0}': {1}", dave, dict.ContainsKey(dave));
+

          
+
    // キーdaveに該当する要素の値を上書き
+
    dict[dave] = new Uri("http://exmaple.net/~dave/");
+

          
+
    foreach (KeyValuePair<Account, Uri> pair in dict)
+
    {
+
      Console.WriteLine("{0,-10} => {1}", pair.Key, pair.Value);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections.Generic
+

          
+
Class Account
+
  Public ID As Integer
+
  Public Name As String
+

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

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

          
+
' AccountクラスのためのEqualityComparer
+
Class AccountComparer
+
  Inherits EqualityComparer(Of Account)
+

          
+
  Public Overrides Function Equals(ByVal x As Account, ByVal y As Account) As Boolean
+
    If x Is Nothing AndAlso y Is Nothing Then
+
      ' xとyがともにNothingの場合は等しいものとする
+
      Return True
+
    Else If x Is Nothing OrElse y Is Nothing Then
+
      ' xとyのどちらかがnullの場合は異なるものとする
+
      Return False
+
    Else
+
      ' フィールドIDが等しければ二つのAccountは等しいものとする
+
      Return x.ID = y.ID
+
    End If
+
  End Function
+

          
+
  Public Overrides Function GetHashCode(ByVal obj As Account) As Integer
+
    ' Nothingの場合はArgumentNullExceptionをスローする
+
    If obj Is Nothing Then Throw New ArgumentNullException("obj")
+

          
+
    ' ハッシュ値にフィールドIDの値を使用する
+
    Return obj.ID
+
  End Function
+
End Class
+

          
+

          
+
Class Sample
+
  Shared Sub Main()
+
    ' アカウントとコンタクト先のディクショナリ
+
    Dim dict As New Dictionary(Of Account, Uri)(New AccountComparer())
+

          
+
    Dim alice   As New Account(2, "Alice")
+
    Dim bob     As New Account(0, "Bob")
+
    Dim charlie As New Account(1, "Charlie")
+

          
+
    dict(alice)   = New Uri("mailto:alice@example.net")
+
    dict(bob)     = New Uri("mailto:bob@example.net")
+
    dict(charlie) = New Uri("mailto:charlie@example.net")
+

          
+
    For Each pair As KeyValuePair(Of Account, Uri) In dict
+
      Console.WriteLine("{0,-10} => {1}", pair.Key, pair.Value)
+
    Next
+

          
+
    Console.WriteLine()
+

          
+
    ' IDフィールドはBobと同じ、NameフィールドはBobと異なるインスタンスdaveを作成
+
    Dim dave As New Account(0, "Dave")
+

          
+
    ' キーbob, daveに該当する要素が存在するかどうか
+
    Console.WriteLine("ContainsKey '{0}': {1}", bob,  dict.ContainsKey(bob))
+
    Console.WriteLine("ContainsKey '{0}': {1}", dave, dict.ContainsKey(dave))
+

          
+
    ' キーdaveに該当する要素の値を上書き
+
    dict(dave) = new Uri("http://exmaple.net/~dave/")
+

          
+
    For Each pair As KeyValuePair(Of Account, Uri) In dict
+
      Console.WriteLine("{0,-10} => {1}", pair.Key, pair.Value)
+
    Next
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
+

          
+
#prompt{{
+
2:Alice    => mailto:alice@example.net
+
0:Bob      => mailto:bob@example.net
+
1:Charlie  => mailto:charlie@example.net
+

          
+
ContainsKey '0:Bob': True
+
ContainsKey '0:Dave': True
+
2:Alice    => mailto:alice@example.net
+
0:Bob      => http://exmaple.net/~dave/
+
1:Charlie  => mailto:charlie@example.net
+
}}
 

        

        
~
この例ではジェネリックなコレクションであるDictionaryを使っていますが、非ジェネリックなコレクションであるHashtableを使ってもDictionaryと同様に動作します。
        ' InitializeComponent() 呼び出しの後に初期化を追加します。
 

        

        
~
**StringComparer
    End Sub
+
既に解説した&msdn(netfx,type,System.StringComparer){StringComparerクラス};は、IComparer・IComparer<string>だけでなくIEqualityComparer・IEqualityComparer<string>も実装しています。 以下の例では、StringComparerを使って大文字小文字を無視するDictionaryを作成しています。 StringComparerの詳細については、[[StringComparerについての個別の解説>programming/netfx2/overview/stringoperation#StringComparisonStringComparer]]を参照してください。
 

        

        
~
#tabpage(C#)
    ' Form は dispose をオーバーライドしてコンポーネント一覧を消去します。
~
#code(cs){{
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
~
using System;
        If disposing Then
~
using System.Collections.Generic;
            If Not (components Is Nothing) Then
~

          
                components.Dispose()
~
class Sample
            End If
~
{
        End If
~
  static void Main()
        MyBase.Dispose(disposing)
~
  {
    End Sub
+
    Dictionary<string, Uri> dict = new Dictionary<string, Uri>(StringComparer.CurrentCultureIgnoreCase);
+

          
+
    dict["Alice"]   = new Uri("mailto:alice@example.net");
+
    dict["Bob"]     = new Uri("mailto:bob@example.net");
+
    dict["Charlie"] = new Uri("mailto:charlie@example.net");
+

          
+
    foreach (KeyValuePair<string, Uri> pair in dict)
+
    {
+
      Console.WriteLine("{0,-10} => {1}", pair.Key, pair.Value);
+
    }
+

          
+
    Console.WriteLine();
+

          
+
    // 各キーに該当する要素の値を上書き
+
    dict["ALICE"]   = new Uri("http://example.net/~alice/");
+
    dict["ChArLiE"] = new Uri("mailto:charlie2@mail.example.com");
+

          
+
    foreach (KeyValuePair<string, Uri> pair in dict)
+
    {
+
      Console.WriteLine("{0,-10} => {1}", pair.Key, pair.Value);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections.Generic
 

        

        
~
Class Sample
    ' Windows フォーム デザイナで必要です。
~
  Shared Sub Main()
    Private components As System.ComponentModel.IContainer
+
    Dim dict As New Dictionary(Of String, Uri)(StringComparer.CurrentCultureIgnoreCase)
+

          
+
    dict("Alice")   = new Uri("mailto:alice@example.net")
+
    dict("Bob")     = new Uri("mailto:bob@example.net")
+
    dict("Charlie") = new Uri("mailto:charlie@example.net")
+

          
+
    For Each pair As KeyValuePair(Of String, Uri) In dict
+
      Console.WriteLine("{0,-10} => {1}", pair.Key, pair.Value)
+
    Next
+

          
+
    Console.WriteLine()
+

          
+
    ' 各キーに該当する要素の値を上書き
+
    dict("ALICE")   = new Uri("http://example.net/~alice/")
+
    dict("ChArLiE") = new Uri("mailto:charlie2@mail.example.com")
+

          
+
    For Each pair As KeyValuePair(Of String, Uri) In dict
+
      Console.WriteLine("{0,-10} => {1}", pair.Key, pair.Value)
+
    Next
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
    ' メモ : 以下のプロシージャは、Windows フォーム デザイナで必要です。
~
Alice      => mailto:alice@example.net
    ' Windows フォーム デザイナを使って変更してください。  
~
Bob        => mailto:bob@example.net
    ' コード エディタは使用しないでください。
~
Charlie    => mailto:charlie@example.net
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
~

          
        '
~
Alice      => http://example.net/~alice/
        'ComparisonDialog
~
Bob        => mailto:bob@example.net
        '
~
Charlie    => mailto:charlie2@mail.example.com
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
~
}}
        Me.ClientSize = New System.Drawing.Size(512, 302)
-
        Me.Name = "ComparisonDialog"
-
        Me.Text = "ListViewとIComparerのサンプル"
 

        

        
~
*構造の比較
    End Sub
+
.NET Framework 4より、&msdn(netfx,id,dd268536){Tuple<T1, T2>};、&msdn(netfx,id,dd387150){Tuple<T1, T2, T3>};などのタプル(組、&msdn(netfx,type,System.Tuple){Tuple};)が導入されました。 これに伴い、構造を比較するためのインターフェイス&msdn(netfx,type,System.Collections.IStructuralComparable){IStructuralComparable};および&msdn(netfx,type,System.Collections.IStructuralEquatable){IStructuralEquatable};が導入され、タプル型やArrayクラスで実装されています。
 

        

        
~
**IStructuralEquatable
#End Region
+
&msdn(netfx,type,System.Collections.IStructuralEquatable){IStructuralEquatable};(System.Collections名前空間)は構造上の等価性を比較するメソッドを提供するインターフェイスです。 例えば、配列を一つの構造体ととらえたとき、同一の要素数(長さ)の配列は同じ構造を持っていると考えることが出来、構造内の個々の要素が等しければ構造的に等しいと定義することが出来ます。
 

        

        
~
構造の比較を行う&msdn(netfx,method,System.Collections.IStructuralEquatable.Equals){IStructuralEquatable.Equalsメソッド};は、比較対象となるオブジェクトと、等価性の比較を行うためのIEqualityComparerの二つを引数に取ります。 .NET Framework 4以降では、ArrayクラスはIStructuralEquatableインターフェイスを実装しているため、構造の比較が行えるようになっています。 ただし、IStructuralEquatableのメソッドは明示的な実装となっているため、IStructuralEquatableにキャストしない限りメソッドを呼び出すことは出来ません。 次の例は、配列同士をIStructuralEquatable.Equalsメソッドで比較する例です。
    ' リストビュー
-
    Private listView As listView = Nothing
 

        

        
~
#tabpage(C#)
    ' リストビューソート順
~
#code(cs){{
    Private listViewSortOrder As SortOrder
+
using System;
+
using System.Collections;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    int[] arr1 = new int[] {0, 1, 2};
+
    int[] arr2 = new int[] {0, 1, 2};
+
    int[] arr3 = new int[] {0, 1, 3};
+
    int[] arr4 = new int[] {0, 1, 2, 3};
+

          
+
    Console.WriteLine("Array.Equals arr1, arr2 : {0}", arr1.Equals(arr2));
+
    Console.WriteLine("Array.Equals arr1, arr3 : {0}", arr1.Equals(arr3));
+
    Console.WriteLine("Array.Equals arr1, arr4 : {0}", arr1.Equals(arr4));
+

          
+
    IStructuralEquatable st1 = arr1;
+

          
+
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr2 : {0}",
+
                      st1.Equals(arr2, StructuralComparisons.StructuralEqualityComparer));
+
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr3 : {0}",
+
                      st1.Equals(arr3, StructuralComparisons.StructuralEqualityComparer));
+
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr4 : {0}",
+
                      st1.Equals(arr4, StructuralComparisons.StructuralEqualityComparer));
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections
 

        

        
+
Class Sample
+
  Shared Sub Main()
+
    Dim arr1() As Integer = New Integer() {0, 1, 2}
+
    Dim arr2() As Integer = New Integer() {0, 1, 2}
+
    Dim arr3() As Integer = New Integer() {0, 1, 3}
+
    Dim arr4() As Integer = New Integer() {0, 1, 2, 3}
+

          
+
    Console.WriteLine("Array.Equals arr1, arr2 : {0}", arr1.Equals(arr2))
+
    Console.WriteLine("Array.Equals arr1, arr3 : {0}", arr1.Equals(arr3))
+
    Console.WriteLine("Array.Equals arr1, arr4 : {0}", arr1.Equals(arr4))
+

          
+
    Dim st1 As IStructuralEquatable = arr1
+

          
+
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr2 : {0}",
+
                      st1.Equals(arr2, StructuralComparisons.StructuralEqualityComparer))
+
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr3 : {0}",
+
                      st1.Equals(arr3, StructuralComparisons.StructuralEqualityComparer))
+
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr4 : {0}",
+
                      st1.Equals(arr4, StructuralComparisons.StructuralEqualityComparer))
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
    ' フォームのLoadイベントハンドラ
~
Array.Equals arr1, arr2 : False
    Private Sub ComparisonDialog_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
+
Array.Equals arr1, arr3 : False
+
Array.Equals arr1, arr4 : False
+
IStructuralEquatable.Equals arr1, arr2 : True
+
IStructuralEquatable.Equals arr1, arr3 : False
+
IStructuralEquatable.Equals arr1, arr4 : False
+
}}
 

        

        
~
Array.Equalsメソッドはオーバーライドされていないためオブジェクト同士の参照の比較しか行いませんが、&msdn(netfx,id,dd783207){Array.IStructuralEquatable.Equalsメソッド};では構造の比較を行うようになっているため、上記のような結果となります。 要素数とすべての要素が等しければ''構造上等しい''となるのに対し、要素数またはいずれかの要素が異なれば''構造上異なる''と判断されます。
        ' リストビューのインスタンス
-
        listView = New ListView()
 

        

        
~
Arrayとは異なり、TupleのEqualsメソッドは構造の比較を行うようになっているため、必ずしもIStructuralEquatableにキャストする必要はありません。
        ' コントロールリストに追加
-
        Me.Controls.Add(listView)
 

        

        
~
#tabpage(C#)
        ' 各プロパティを設定
~
#code(cs){{
        With listView
+
using System;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Tuple<int, int, int> t1 = Tuple.Create(0, 1, 2);
+
    Tuple<int, int, int> t2 = Tuple.Create(0, 1, 2);
+
    Tuple<int, int, int> t3 = Tuple.Create(0, 1, 3);
+
    Tuple<int, int, int, int> t4 = Tuple.Create(0, 1, 2, 3);
+
    Tuple<int, double, int> t5 = Tuple.Create(0, 1.0, 2);
+

          
+
    Console.WriteLine("Tuple.Equals t1, t2 : {0}", t1.Equals(t2));
+
    Console.WriteLine("Tuple.Equals t1, t3 : {0}", t1.Equals(t3));
+
    Console.WriteLine("Tuple.Equals t1, t4 : {0}", t1.Equals(t4));
+
    Console.WriteLine("Tuple.Equals t1, t5 : {0}", t1.Equals(t5));
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections
 

        

        
~
Class Sample
            .Location = New Point(20, 20) ' 位置
~
  Shared Sub Main()
            .Size = New Size(Me.ClientSize.Width - 40, Me.ClientSize.Height - 40) 'サイズ
~
    Dim t1 As Tuple(Of Integer, Integer, Integer) = Tuple.Create(0, 1, 2)
            .View = View.Details ' ビューのタイプを「詳細」にする
~
    Dim t2 As Tuple(Of Integer, Integer, Integer) = Tuple.Create(0, 1, 2)
            .HeaderStyle = ColumnHeaderStyle.Clickable ' 項目タブをクリック可能にしておく
+
    Dim t3 As Tuple(Of Integer, Integer, Integer) = Tuple.Create(0, 1, 3)
+
    Dim t4 As Tuple(Of Integer, Integer, Integer, Integer) = Tuple.Create(0, 1, 2, 3)
+
    Dim t5 As Tuple(Of Integer, Double, Integer) = Tuple.Create(0, 1.0, 2)
+

          
+
    Console.WriteLine("Tuple.Equals t1, t2 : {0}", t1.Equals(t2))
+
    Console.WriteLine("Tuple.Equals t1, t3 : {0}", t1.Equals(t3))
+
    Console.WriteLine("Tuple.Equals t1, t4 : {0}", t1.Equals(t4))
+
    Console.WriteLine("Tuple.Equals t1, t5 : {0}", t1.Equals(t5))
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
            ' イベントハンドラを追加
~
Tuple.Equals t1, t2 : True
            AddHandler .ColumnClick, AddressOf listView_ColumnClick
+
Tuple.Equals t1, t3 : False
+
Tuple.Equals t1, t4 : False
+
Tuple.Equals t1, t5 : False
+
}}
 

        

        
~
なお、System.Collections名前空間やSystem.Collections.Generic名前空間にあるList<T>などのコレクションクラスにはIStructuralEquatableが実装されていないため、この様な比較を行うことは出来ません。 要素数を調べ、個々の要素に対して等しいかどうか比較を行う必要があります。
        End With
 

        

        
~
**IStructuralComparable
        ' 項目タブを作る
~
&msdn(netfx,type,System.Collections.IStructuralComparable){IStructuralComparable};(System.Collections名前空間)はIStructuralEquatableと同様に構造上の大小関係を比較するメソッドを提供するインターフェイスです。
        With listView.Columns
 

        

        
~
構造の比較を行う&msdn(netfx,method,System.Collections.IStructuralComparable.CompareTo){IStructuralComparable.CompareToメソッド};は、比較対象となるオブジェクトと、大小関係の比較を行うためのIComparerの二つを引数に取ります。
            .Add("名前", listView.Width * 0.4, HorizontalAlignment.Left)
-
            .Add("生年月日", listView.Width * 0.25, HorizontalAlignment.Left)
-
            .Add("年齢", listView.Width * 0.15, HorizontalAlignment.Left)
-
            .Add("血液型", listView.Width * 0.15, HorizontalAlignment.Left)
 

        

        
~
&msdn(netfx,member,System.Collections.StructuralComparisons.StructuralComparer){StructuralComparisons.StructuralComparerプロパティ};を参照すると、構造の比較を行うIComparerを取得することが出来ます。 次の例では、ジャグ配列を作成し、Array.Sortメソッドでソートしています。
        End With
 

        

        
~
#tabpage(C#)
        ' アイテムを追加する
~
#code(cs){{
        AddListViewItem(listView, "飯田圭織", New DateTime(1981, 8, 8), "A")
~
using System;
        AddListViewItem(listView, "安倍なつみ", New DateTime(1981, 8, 10), "A")
~
using System.Collections;
        AddListViewItem(listView, "保田圭", New DateTime(1980, 12, 6), "A")
~

          
        AddListViewItem(listView, "矢口真里", New DateTime(1983, 1, 20), "A")
~
class Sample
        AddListViewItem(listView, "吉澤ひとみ", New DateTime(1985, 4, 12), "O")
~
{
        AddListViewItem(listView, "石川梨華", New DateTime(1985, 1, 19), "A")
~
  static void Main()
        AddListViewItem(listView, "辻希美", New DateTime(1987, 6, 17), "O")
~
  {
        AddListViewItem(listView, "加護亜依", New DateTime(1988, 2, 7), "AB")
~
    int[][] arr = new int[][] {
        AddListViewItem(listView, "小川麻琴", New DateTime(1987, 10, 29), "O")
~
      new int[] {1, 1, 0},
        AddListViewItem(listView, "新垣里沙", New DateTime(1988, 10, 20), "B")
~
      new int[] {0, 0, 0},
        AddListViewItem(listView, "高橋愛", New DateTime(1986, 9, 14), "A")
~
      new int[] {0, 1, 1},
        AddListViewItem(listView, "紺野あさ美", New DateTime(1987, 5, 7), "B")
~
      new int[] {0, 1, 0},
        AddListViewItem(listView, "後藤真希", New DateTime(1985, 9, 23), "O")
~
      new int[] {0, 0, 1},
        AddListViewItem(listView, "松浦亜弥", New DateTime(1986, 6, 25), "B")
~
      new int[] {1, 0, 0},
        AddListViewItem(listView, "藤本美貴", New DateTime(1985, 2, 26), "A")
+
      new int[] {1, 1, 1},
+
      new int[] {1, 0, 1},
+
    };
+

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

          
+
    Print(arr);
+

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

          
+
    Array.Sort(arr, StructuralComparisons.StructuralComparer);
+
    // StructuralComparisons.StructuralComparerを指定しない場合、InvalidOperationExceptionがスローされる
+
    // Array.Sort(arr);
+

          
+
    Print(arr);
+
  }
+

          
+
  static void Print(int[][] arr)
+
  {
+
    foreach (int[] a in arr)
+
    {
+
      Console.WriteLine("{{{0}}}", string.Join(", ", a));
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections
 

        

        
~
Class Sample
        ' ソート順を設定しておく
~
  Shared Sub Main()
        listViewSortOrder = SortOrder.Ascending
+
    Dim arr()() As Integer = New Integer()() { _
+
      New Integer() {1, 1, 0}, _
+
      New Integer() {0, 0, 0}, _
+
      New Integer() {0, 1, 1}, _
+
      New Integer() {0, 1, 0}, _
+
      New Integer() {0, 0, 1}, _
+
      New Integer() {1, 0, 0}, _
+
      New Integer() {1, 1, 1}, _
+
      New Integer() {1, 0, 1} _
+
    }
+

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

          
+
    Print(arr)
+

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

          
+
    Array.Sort(arr, StructuralComparisons.StructuralComparer)
+
    ' StructuralComparisons.StructuralComparerを指定しない場合、InvalidOperationExceptionがスローされる
+
    ' Array.Sort(arr);
+

          
+
    Print(arr)
+
  End Sub
+

          
+
  Shared Sub Print(ByVal arr()() As Integer)
+
    For Each a As Integer() In arr
+
      Console.WriteLine("{{{0}}}", string.Join(", ", a))
+
    Next
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
    End Sub
+
[before sort]
+
{1, 1, 0}
+
{0, 0, 0}
+
{0, 1, 1}
+
{0, 1, 0}
+
{0, 0, 1}
+
{1, 0, 0}
+
{1, 1, 1}
+
{1, 0, 1}
+
[after sort]
+
{0, 0, 0}
+
{0, 0, 1}
+
{0, 1, 0}
+
{0, 1, 1}
+
{1, 0, 0}
+
{1, 0, 1}
+
{1, 1, 0}
+
{1, 1, 1}
+
}}
 

        

        
~
Arrayとは異なり、TupleのCompareToメソッドは構造の比較を行うようになっているため、必ずしもStructuralComparisons.StructuralComparerを指定する必要はありません。
    ' リストビューにアイテムを追加する
-
    Private Sub AddListViewItem(ByVal list As listView, ByVal name As String, ByVal birthDate As DateTime, ByVal bloodType As String)
 

        

        
~
#tabpage(C#)
        ' 新たなアイテムのインスタンスを作成
~
#code(cs){{
        Dim item As New ListViewItem()
+
using System;
+
using System.Collections;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Tuple<int, string>[] arr = new Tuple<int, string>[] {
+
      Tuple.Create(1, "Eve"),
+
      Tuple.Create(4, "Dave"),
+
      Tuple.Create(2, "Alice"),
+
      Tuple.Create(0, "Charlie"),
+
      Tuple.Create(3, "Bob"),
+
    };
+

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

          
+
    Print(arr);
+

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

          
+
    Array.Sort(arr);
+

          
+
    Print(arr);
+

          
+
    Console.WriteLine("[after sort (StructuralComparisons.StructuralComparer)]");
+

          
+
    Array.Sort(arr, StructuralComparisons.StructuralComparer);
+

          
+
    Print(arr);
+
  }
+

          
+
  static void Print(Tuple<int, string>[] arr)
+
  {
+
    foreach (Tuple<int, string> e in arr)
+
    {
+
      Console.WriteLine(e);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Collections
 

        

        
~
Class Sample
        ' 年齢を計算
~
  Shared Sub Main()
        Dim age As Integer = DateTime.Now.Year - birthDate.Year
~
    Dim arr() As Tuple(Of Integer, String) = New Tuple(Of Integer, String)() { _
        Dim birthDayInThisYear As New DateTime(DateTime.Now.Year, birthDate.Month, birthDate.Day)
~
      Tuple.Create(1, "Eve"), _
        If birthDayInThisYear > DateTime.Now Then age -= 1
+
      Tuple.Create(4, "Dave"), _
+
      Tuple.Create(2, "Alice"), _
+
      Tuple.Create(0, "Charlie"), _
+
      Tuple.Create(3, "Bob") _
+
    }
 

        

        
~
    Console.WriteLine("[before sort]")
        ' アイテムのプロパティを設定 ( アイテムは文字列としてしか登録できない )
-
        With item
-
            .Text = name
-
            .SubItems.Add(birthDate.ToShortDateString())
-
            .SubItems.Add(age.ToString())
-
            .SubItems.Add(bloodType)
-
        End With
 

        

        
~
    Print(arr)
        ' リストに追加
-
        list.Items.Add(item)
 

        

        
~
    Console.WriteLine("[after sort]")
    End Sub
 

        

        
~
    Array.Sort(arr)
    ' リストビューのColumnClickイベントハンドラ ( 項目タブをクリックする度に呼び出される )
-
    Private Sub listView_ColumnClick(ByVal sender As Object, ByVal e As ColumnClickEventArgs)
 

        

        
~
    Print(arr)
        ' 昇順・降順を切り替え
-
        If listViewSortOrder = SortOrder.Ascending Then
-
            listViewSortOrder = SortOrder.Descending
-
        Else
-
            listViewSortOrder = SortOrder.Ascending
-
        End If
 

        

        
~
    Console.WriteLine("[after sort (StructuralComparisons.StructuralComparer)]")
        ' 比較担当を作成 ( e.Columnはクリックされた項目タブのインデックス )
-
        Dim comp As New Comparer(e.Column, listViewSortOrder)
 

        

        
~
    Array.Sort(arr, StructuralComparisons.StructuralComparer)
        ' 比較開始 ( このプロパティにICopmarerを指定すると自動的にソートが開始される )
-
        listView.ListViewItemSorter = comp
 

        

        
~
    Print(arr)
    End Sub
+
  End Sub
 

        

        
+
  Shared Sub Print(ByVal arr() As Tuple(Of Integer, String))
+
    For Each e As Tuple(Of Integer, String) In arr
+
      Console.WriteLine(e)
+
    Next
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
#ref(0.jpg,生年月日タブをクリックして生年月日順に並べ替えたところ)
+
[before sort]
+
(1, Eve)
+
(4, Dave)
+
(2, Alice)
+
(0, Charlie)
+
(3, Bob)
+
[after sort]
+
(0, Charlie)
+
(1, Eve)
+
(2, Alice)
+
(3, Bob)
+
(4, Dave)
+
[after sort (StructuralComparisons.StructuralComparer)]
+
(0, Charlie)
+
(1, Eve)
+
(2, Alice)
+
(3, Bob)
+
(4, Dave)
+
}}