IComparableとIComparerを使ったソート

注意:
この文書は以前「.NETでいきまっしょい!」で公開していたものですが、公開以降メンテナンスされていません。 今や古い情報となった内容が記載されている場合があるのでご注意ください。

1.ArrayListとソート


 今回はIComparableとIComparerを使ったソートについて見てみます。 それに先だって、ArrayListクラスを用いて最も簡単なソートを行ってみようと思います。 ここではArrayListの詳細な説明はしませんが、ArrayListは通常の配列とは異なり、好きなときにアイテムを追加・削除でき、動的にアイテム数を変えることができるという特徴があります。 また、このArrayListクラスから配列を作成することもできます。 それでは早速、ArrayListとそれを用いたソートのサンプルコードを組んでみます。
ArrayListとソート
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
Imports System
Imports System.Collections

Public Class Comparison

    Public Shared Function Main(ByVal args() As String) As Integer

        Dim arr As New ArrayList()

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


        Dim name As String

        Console.WriteLine("   Before sorting...")

        For Each name In arr

            Console.WriteLine(name)

        Next

        arr.Sort()

        Console.WriteLine()
        Console.WriteLine("   After sorting...")

        For Each name In arr

            Console.WriteLine(name)

        Next

        Return 0

    End Function

End Class
出力結果
   Before sorting...
GOTO Maki
KAGO Ai
TSUJI Nozomi
MATSUURA Aya
KONNO Asami
FUJIMOTO Miki

   After sorting...
FUJIMOTO Miki
GOTO Maki
KAGO Ai
KONNO Asami
MATSUURA Aya
TSUJI Nozomi
Press any key to continue

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

2.ソートできるオブジェクト・できないオブジェクト


 先ほどはArrayListに文字列を追加しましたが、数値も追加できます。 ソースコードは割愛しますが、先ほどのソースコードにおいてアイテムを追加した部分を文字列から数値(IntegerとDouble)に変えて実行した結果です。
IntegerでのソートDoubleでのソート
   Before sorting...
3
1
4
1
5
9
2
7

   After sorting...
1
1
2
3
4
5
7
9
Press any key to continue
   Before sorting...
3.1415927
1.7320508
2.7182818
1.41421356
2.2360679

   After sorting...
1.41421356
1.7320508
2.2360679
2.7182818
3.1415927
Press any key to continue

 この調子で行けばどのような型でもソートできそうな感じなのですが、実際にはそうはいきません。 次のコードはTestClassというクラスを新たに作成し、そのインスタンスをArrayListに追加してソートさせるコードです。
ソートに失敗する例
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
Imports System
Imports System.Collections

Public Class TestClass

    Public str As String

    Public Sub New(ByVal str As String)
        Me.str = str
    End Sub

    Public Overrides Function ToString() As String
        Return Me.str
    End Function

End Class

Public Class Comparison

    Public Shared Function Main(ByVal args() As String) As Integer

        Dim arr As New ArrayList()

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


        Dim inst As TestClass

        Console.WriteLine("   Before sorting...")

        For Each inst In arr

            Console.WriteLine(inst)

        Next

        arr.Sort()

        Console.WriteLine()
        Console.WriteLine("   After sorting...")

        For Each inst In arr

            Console.WriteLine(inst)

        Next

        Return 0

    End Function

End Class
Doubleでのソート
   Before sorting...
どんな型でも
ArrayListは
ソートして
くれるだろうか

ハンドルされていない例外 : System.InvalidOperationException: 指定された ICompare
r が例外をスローしました。 ---> System.ArgumentException: 少なくとも 1 つのオブ
ジェクトで IComparable を実装しなければなりません。
   at System.Collections.Comparer.Compare(Object a, Object b)
   at System.SorterObjectArray.QuickSort(Int32 left, Int32 right)
   --- 内部例外スタック トレースの終わり ---
   at System.SorterObjectArray.QuickSort(Int32 left, Int32 right)
   at System.Array.Sort(Array keys, Array items, Int32 index, Int32 length, ICom
parer comparer)
   at System.Collections.ArrayList.Sort(Int32 index, Int32 count, IComparer comp
arer)
   at System.Collections.ArrayList.Sort()
   at SampleConsoleApplication.MainModule.Main() in D:\Visual Basic .NET Program
\SampleConsoleApplication\Module1.vb:line 148
Press any key to continue

 実行結果のように、Sort()メソッドを呼び出したとたん例外エラーが発生し、ソートに失敗します。 IntegerやString型は数値・文字列なので現実的に「比較」できるのですが、TestClassのような場合文字列が含まれているとはいえ、どのように比較していいのかわかりません。 そのためこのような例外エラーが発生します。
 しかし、この例外エラーのメッセージをよく見てみると、
ハンドルされていない例外 : System.InvalidOperationException: 指定された IComparer が例外をスローしました。 ---> System.ArgumentException: 少なくとも 1 つのオブジェクトで IComparable を実装しなければなりません。
 というように書いてあります。 実はソートできるかできないかを決めるカギはこのIComparableインターフェイスにあるのです。 つまり、ソートを行うには、そのクラスがIComparableを実装していればいいのです。 Integer型のエイリアスであるSystem.Int32構造体はIComparableなどを含むいくつかのインターフェイスを実装しています。 そのためにソートができたのです。

3.クラスにIComparableを実装する


 それでは早速IComparableインターフェイスを実装してみます。 IComparableインターフェイスを実装する場合は一つのメソッドCompareTo()を実装しなければなりません。 このメソッドの詳細は次のようなものです。


・一つのObject型を引数にとり、Integer型の値を返す。 つまりシグニチャは「Function CompareTo( ByVal obj As Object ) As Integer
・objとこの(インターフェイスを実装する)インスタンスについて、
    objよりこのインスタンスが大きいと比較できるときは 0より大きい値
    objとこのインスタンスが同じと比較できるときは 0
    objよりこのインスタンスが小さいと比較できるときは 0より小さい値
を関数の戻り値として返す。


 このことを考慮して、IComparableを実装したクラスの例を次に示します。 このクラスでは、名前・誕生日・年齢をメンバとして持つクラスで、CompareTo()メソッドでは年齢によって比較を行います。
IComparableを実装したクラス
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
Imports System
Imports System.Collections

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

    ' フィールド
    Public Name As String
    Public Age As Integer
    Public BirthDate As DateTime

    ' コンストラクタ
    Public Sub New()
    End Sub

    Public Sub New(ByVal Name As String, ByVal BirthDate As DateTime)

        MyClass.Name = Name
        MyClass.BirthDate = BirthDate

        ' 年齢を計算
        MyClass.Age = DateTime.Now.Year - BirthDate.Year

        Dim birthDayInThisYear As New DateTime(DateTime.Now.Year, BirthDate.Month, BirthDate.Day)

        If birthDayInThisYear > DateTime.Now Then Me.Age -= 1

    End Sub

    ' ToString()メソッドをオーバーライド
    Public Overrides Function ToString() As String

        Return Name + "(" + BirthDate.ToLongDateString() + "生まれ, " + Age.ToString() + "歳)"

    End Function

    ' IComparable.CompareTo()メソッドを実装
    Public Function CompareTo(ByVal x As Object) As Integer Implements IComparable.CompareTo

        ' 渡されたオブジェクトの型を確認
        If TypeOf x Is Girl Then

            Dim g As Girl = CType(x, Girl)

            If Me.Age > g.Age Then

                ' このインスタンスのAgeが渡されたインスタンスのAgeより大きいとき
                Return 1

            ElseIf Me.Age = g.Age Then

                ' このインスタンスのAgeが渡されたインスタンスのAgeと同じとき
                Return 0

            ElseIf Me.Age < g.Age Then

                ' このインスタンスのAgeが渡されたインスタンスのAgeより小さいとき
                Return -1

            End If

        End If

        ' 比較できないとき
        Return 0

    End Function

End Class

 CompareTo()メソッドでは引数の型がObject型なので一度渡されたオブジェクトの型を確認してから、自分のクラス型であるGirl型にキャストします。 その後渡されたインスタンスと自分自身を比較して適切な値を返してやります。 ArrayListにGirl型以外のインスタンスが格納されていた場合など、比較できない型が渡された場合に備え、そのような場合には等価であると定義しておきます。 実際にこのクラスを用いた例が次のコードです。
ソートのサンプル
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
Imports System
Imports System.Collections

Public Class Comparison

    Public Shared Function Main(ByVal args() As String) As Integer

        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)))

        Dim g As Girl

        Console.WriteLine("    Before sorting...")

        For Each g In arr

            Console.WriteLine(g)

        Next

        arr.Sort()

        Console.WriteLine("    After sorting...")

        For Each g In arr

            Console.WriteLine(g)

        Next

        Return 0

    End Function

End Class
出力結果
       Before sorting...
後藤真希(1985年9月23日生まれ, 18歳)
加護亜依(1988年2月7日生まれ, 15歳)
辻希美(1987年6月17日生まれ, 16歳)
松浦亜弥(1986年6月25日生まれ, 17歳)
紺野あさ美(1987年5月7日生まれ, 16歳)
藤本美貴(1985年2月26日生まれ, 18歳)
    After sorting...
加護亜依(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

 この結果を見るとわかるとおり、CompareTo()では年齢という数値だけを比較しているので、誕生日の順番は考慮されていません。 誕生日で比較させる場合にはCompareTo()メソッド内における比較処理を誕生日で行うように変えてやります。 その場合のソースコードと実行結果は次のようになります。
比較方法を変える
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
Imports System
Imports System.Collections

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

    ' IComparable.CompareTo()メソッドを実装
    Public Function CompareTo(ByVal x As Object) As Integer Implements IComparable.CompareTo

        ' 渡されたオブジェクトの型を確認
        If TypeOf x Is Girl Then

            Dim g As Girl = CType(x, Girl)

            If Me.BirthDate > g.BirthDate Then

                ' このインスタンスのBirthDateが渡されたインスタンスのBirthDateより大きいとき
                Return 1

            ElseIf Me.BirthDate = g.BirthDate Then

                ' このインスタンスのBirthDateが渡されたインスタンスのBirthDateと同じとき
                Return 0

            ElseIf Me.BirthDate < g.BirthDate Then

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

            End If

        End If

        ' 比較できないとき
        Return 0

    End Function

End Class
出力結果
    Before sorting...
後藤真希(1985年9月23日生まれ, 18歳)
加護亜依(1988年2月7日生まれ, 15歳)
辻希美(1987年6月17日生まれ, 16歳)
松浦亜弥(1986年6月25日生まれ, 17歳)
紺野あさ美(1987年5月7日生まれ, 16歳)
藤本美貴(1985年2月26日生まれ, 18歳)
    After sorting...
藤本美貴(1985年2月26日生まれ, 18歳)
後藤真希(1985年9月23日生まれ, 18歳)
松浦亜弥(1986年6月25日生まれ, 17歳)
紺野あさ美(1987年5月7日生まれ, 16歳)
辻希美(1987年6月17日生まれ, 16歳)
加護亜依(1988年2月7日生まれ, 15歳)
Press any key to continue

4.IComparerを使ったソート


 IComparableインターフェイスをクラスに実装すれば比較を行えるようになるのですが、先ほどの例のようにソート方法を変えたい場合はコードを書き換えなければなりません。 ソート方法が誕生日だけでいい場合は問題ありませんが、年齢順や名前順でソートしたくなる場合が出てきます。 そのような場合はIComparableインターフェイスではなくIComaparerインターフェイスを用います。
 このインターフェイスは比較を行いたいクラスに実装するのではなく、比較を行わせるクラスに実装します。 つまり、比較対照と比較方法を区別することができるのです。 IComparableの時と同様に、IComparerを実装する場合はCompare()メソッドを実装する必要があります。 その詳細は次のようなものです。


・二つのObject型を引数にとり、Integer型の値を返す。 つまりシグニチャは「Function CompareTo( ByVal x As Object, ByVal y As Object ) As Integer
・xとyについて、
    xよりyが大きいと比較できるときは 0より大きい値
    xとyが同じと比較できるときは 0
    xよりyが小さいと比較できるときは 0より小さい値
を関数の戻り値として返す。


 Compare()メソッドはCompareTo()とは異なり二つのオブジェクトを引数にとります。 CompareTo()メソッドは自分自身とを比較したのに対し、Compare()メソッドでは自分自身は比較方法を定義するクラスで、比較対照ではないからです。 後の戻り値はCompareTo()と大差ありません。 これを実装したクラスの例を示します。
IComparerを使ったソート
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
Imports System
Imports System.Collections

' 比較方法を設定する列挙型
Public Enum GirlComparisonMode As Integer

    ByName = 0
    ByBirthDate = 1
    ByAge = 2

End Enum

' Girlクラスの比較を行うクラス
Public Class GirlComparer
    Implements IComparer

    ' 比較方法
    Public ComparisonMode As GirlComparisonMode

    ' コンストラクタ
    Public Sub New()
        ComparisonMode = GirlComparisonMode.ByName
    End Sub

    ' 比較メソッド
    Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare

        ' オブジェクトの型を確認
        If TypeOf x Is Girl AndAlso TypeOf y Is Girl Then

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

            Select Case ComparisonMode

                Case GirlComparisonMode.ByName

                    ' 名前による比較
                    If gx.Name > gy.Name Then

                        Return 1

                    ElseIf gx.Name = gy.Name Then

                        Return 0

                    ElseIf gx.Name < gy.Name Then

                        Return -1

                    End If


                Case GirlComparisonMode.ByBirthDate

                    ' 誕生日による比較
                    If gx.BirthDate > gy.BirthDate Then

                        Return 1

                    ElseIf gx.BirthDate = gy.BirthDate Then

                        Return 0

                    ElseIf gx.BirthDate < gy.BirthDate Then

                        Return -1

                    End If


                Case GirlComparisonMode.ByAge

                    ' 年齢による比較
                    Return gx.Age - gy.Age

            End Select

        End If

        ' 比較できない場合
        Return 0

    End Function

End Class


' Girlクラス
Public Class Girl

    ' フィールド
    Public Name As String
    Public Age As Integer
    Public BirthDate As DateTime

    ' コンストラクタ
    Public Sub New()
    End Sub

    Public Sub New(ByVal Name As String, ByVal BirthDate As DateTime)

        MyClass.Name = Name
        MyClass.BirthDate = BirthDate

        ' 年齢を計算
        MyClass.Age = DateTime.Now.Year - BirthDate.Year

        Dim birthDayInThisYear As New DateTime(DateTime.Now.Year, BirthDate.Month, BirthDate.Day)

        If birthDayInThisYear > DateTime.Now Then Me.Age -= 1

    End Sub

    ' ToString()メソッドをオーバーライド
    Public Overrides Function ToString() As String

        Return Name + "(" + BirthDate.ToLongDateString() + "生まれ, " + Age.ToString() + "歳)"

    End Function

    ' CompareTo()メソッドは必要ない

End Class



Public Class Comparison

    Public Shared Function Main(ByVal args() As String) As Integer

        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)))

        Dim g As Girl

        Console.WriteLine("    Before sorting...")

        For Each g In arr

            Console.WriteLine(g)

        Next

        Dim i As Integer
        Dim c As New GirlComparer()

        For i = 0 To 2

            c.ComparisonMode = i

            arr.Sort(c)

            Console.WriteLine("    Sorted " + c.ComparisonMode.ToString())

            For Each g In arr

                Console.WriteLine(g)

            Next

        Next

        Return 0

    End Function

End Class
出力結果
    Before sorting...
後藤真希(1985年9月23日生まれ, 18歳)
加護亜依(1988年2月7日生まれ, 15歳)
辻希美(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

 実際に比較方法を指定するためには、IComparerを実装したクラスのインスタンスを作成し、Sort()メソッドを呼び出すときにそのインスタンスを引数として渡します。 この例ではForステートメントにより定義した3種類の比較方法を使用してSort()メソッドを呼び出し、その結果を表示しています。
 この例のように比較方法を外部クラスで指定する場合は、比較対照となるクラスはIComparableを実装している必要はありません。 このソースコードでもGirlクラスはIComparableを実装していません。 それに変わって、IComparerを実装したクラスでは適切な比較方法を定義していなければなりません。

5.ListViewコントロールでIComparerを使う


 ListViewコントロールはその特性上、最もよくソートを行うと思います。 また、ソート方法も場合によって様々です。 ListViewコントロールで独自のソート方法を定義するためにIComparerを適用したサンプルを作ってみました。 コメントをふってあるので詳しい説明はしませんが、いくつか応用的なコードもあるのでぜひ参考にして下さい。
ListViewコントロールでIComparerを使う
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
' 比較方法を指定する列挙型
Public Enum ComparisonMode

    ByName = 0
    ByBirthDate = 1
    ByAge = 2
    ByBloodType = 3

End Enum

' 比較を行うクラス
Public Class Comparer
    ' IComparerインターフェイスをインプリメント
    Implements IComparer

    ' 比較方法
    Public ComparisonMode As ComparisonMode

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

    ' コンストラクタ
    Public Sub New()
    End Sub

    Public Sub New(ByVal comparisonMode As ComparisonMode, ByVal sortOrder As SortOrder)

        Me.ComparisonMode = comparisonMode
        Me.SortOrder = sortOrder

    End Sub

    ' 比較を行うメソッド
    Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare

        ' 規定の戻り値
        Dim intCompareResult As Integer = 0

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

        ' 比較対象がListViewItemの時 ( この型で渡される )
        If TypeOf x Is ListViewItem AndAlso TypeOf y Is ListViewItem Then

            ' ListItem型へキャスト
            Dim itemX As ListViewItem = CType(x, ListViewItem)
            Dim itemY As ListViewItem = CType(y, ListViewItem)

            ' 比較方法とアイテムから比較すべき文字列を取得
            Dim textX As String = itemX.SubItems(ComparisonMode).Text
            Dim textY As String = itemY.SubItems(ComparisonMode).Text

            ' 比較方法別の処理
            Select Case ComparisonMode

                Case ComparisonMode.ByAge

                    ' 年齢による比較
                    Dim ageX As Integer = Integer.Parse(textX)
                    Dim ageY As Integer = Integer.Parse(textY)

                    ' その差を結果とする
                    intCompareResult = ageX - ageY

                Case ComparisonMode.ByBirthDate

                    ' 生年月日による比較
                    Dim dtmX As DateTime = DateTime.Parse(textX)
                    Dim dtmY As DateTime = DateTime.Parse(textY)

                    If dtmX > dtmY Then

                        intCompareResult = 1

                    ElseIf dtmX = dtmY Then

                        intCompareResult = 0

                    ElseIf dtmX < dtmY Then

                        intCompareResult = -1

                    End If

                Case ComparisonMode.ByBloodType, ComparisonMode.ByName

                    ' 名前・血液型による比較 ( String型のCompare()メソッドをを流用する )
                    intCompareResult = String.Compare(textX, textY)

            End Select

            ' 昇順降順の設定による戻り値の符号反転
            If SortOrder = SortOrder.Descending Then intCompareResult = -intCompareResult

        End If

        ' 戻り値
        Return intCompareResult

    End Function

End Class


' フォーム
Public Class ComparisonDialog
    Inherits System.Windows.Forms.Form

#Region " Windows フォーム デザイナで生成されたコード "

    Public Sub New()
        MyBase.New()

        ' この呼び出しは Windows フォーム デザイナで必要です。
        InitializeComponent()

        ' InitializeComponent() 呼び出しの後に初期化を追加します。

    End Sub

    ' Form は dispose をオーバーライドしてコンポーネント一覧を消去します。
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    ' Windows フォーム デザイナで必要です。
    Private components As System.ComponentModel.IContainer

    ' メモ : 以下のプロシージャは、Windows フォーム デザイナで必要です。
    ' Windows フォーム デザイナを使って変更してください。  
    ' コード エディタは使用しないでください。
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        '
        'ComparisonDialog
        '
        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

#End Region

    ' リストビュー
    Private listView As listView = Nothing

    ' リストビューソート順
    Private listViewSortOrder As SortOrder


    ' フォームのLoadイベントハンドラ
    Private Sub ComparisonDialog_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        ' リストビューのインスタンス
        listView = New ListView()

        ' コントロールリストに追加
        Me.Controls.Add(listView)

        ' 各プロパティを設定
        With listView

            .Location = New Point(20, 20) ' 位置
            .Size = New Size(Me.ClientSize.Width - 40, Me.ClientSize.Height - 40) 'サイズ
            .View = View.Details ' ビューのタイプを「詳細」にする
            .HeaderStyle = ColumnHeaderStyle.Clickable ' 項目タブをクリック可能にしておく

            ' イベントハンドラを追加
            AddHandler .ColumnClick, AddressOf listView_ColumnClick

        End With

        ' 項目タブを作る
        With listView.Columns

            .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)

        End With

        ' アイテムを追加する
        AddListViewItem(listView, "飯田圭織", New DateTime(1981, 8, 8), "A")
        AddListViewItem(listView, "安倍なつみ", New DateTime(1981, 8, 10), "A")
        AddListViewItem(listView, "保田圭", New DateTime(1980, 12, 6), "A")
        AddListViewItem(listView, "矢口真里", New DateTime(1983, 1, 20), "A")
        AddListViewItem(listView, "吉澤ひとみ", New DateTime(1985, 4, 12), "O")
        AddListViewItem(listView, "石川梨華", New DateTime(1985, 1, 19), "A")
        AddListViewItem(listView, "辻希美", New DateTime(1987, 6, 17), "O")
        AddListViewItem(listView, "加護亜依", New DateTime(1988, 2, 7), "AB")
        AddListViewItem(listView, "小川麻琴", New DateTime(1987, 10, 29), "O")
        AddListViewItem(listView, "新垣里沙", New DateTime(1988, 10, 20), "B")
        AddListViewItem(listView, "高橋愛", New DateTime(1986, 9, 14), "A")
        AddListViewItem(listView, "紺野あさ美", New DateTime(1987, 5, 7), "B")
        AddListViewItem(listView, "後藤真希", New DateTime(1985, 9, 23), "O")
        AddListViewItem(listView, "松浦亜弥", New DateTime(1986, 6, 25), "B")
        AddListViewItem(listView, "藤本美貴", New DateTime(1985, 2, 26), "A")

        ' ソート順を設定しておく
        listViewSortOrder = SortOrder.Ascending

    End Sub

    ' リストビューにアイテムを追加する
    Private Sub AddListViewItem(ByVal list As listView, ByVal name As String, ByVal birthDate As DateTime, ByVal bloodType As String)

        ' 新たなアイテムのインスタンスを作成
        Dim item As New ListViewItem()

        ' 年齢を計算
        Dim age As Integer = DateTime.Now.Year - birthDate.Year
        Dim birthDayInThisYear As New DateTime(DateTime.Now.Year, birthDate.Month, birthDate.Day)
        If birthDayInThisYear > DateTime.Now Then age -= 1

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

        ' リストに追加
        list.Items.Add(item)

    End Sub

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

        ' 昇順・降順を切り替え
        If listViewSortOrder = SortOrder.Ascending Then
            listViewSortOrder = SortOrder.Descending
        Else
            listViewSortOrder = SortOrder.Ascending
        End If

        ' 比較担当を作成 ( e.Columnはクリックされた項目タブのインデックス )
        Dim comp As New Comparer(e.Column, listViewSortOrder)

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

    End Sub

End Class
実行結果
実行結果 (生年月日タブをクリックして生年月日順に並べ替えたところ)