Představte si, že máte kolekci nějakých objektů (např. List) a potřebujete je setřídit podle nějakých složitějších kritérií. Tak mějme například kolekci objektů s informacemi o zaměstnancích a chceme je setřídit podle data narození tak, že budeme ignorovat rok, ale vezmem v úvahu jen měsíc a den (sestavujeme třeba firemní narozeninový kalendář).
Je jasné, že si na to můžeme najít nějaký třídící algoritmus a napsat jej s příslušnou třídící logikou sami, ale je to naprosto zbytečné a je to jen způsob, jak do aplikace dostat blbé chyby. Daleko lepší je využít obecné třídění, které .NET Framework umí a naimplementovat tomuto objektu rozhraní IComparable.
''' <summary>
''' Zaměstnanec
''' </summary>
Public Class Employee
Implements IComparable
Private _name As String
''' <summary>
''' Jméno zaměstnance
''' </summary>
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _birthDate As Date
''' <summary>
''' Datum narození
''' </summary>
Public Property BirthDate() As Date
Get
Return _birthDate
End Get
Set(ByVal value As Date)
_birthDate = value
End Set
End Property
''' <summary>
''' Výpis objektu na text
''' </summary>
Public Overrides Function ToString() As String
Return String.Format("{0,-50} {1:d}", Me.Name, Me.BirthDate)
End Function
''' <summary>
''' Vlastní způsob řazení
''' </summary>
Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
' vytáhnout měsíc a den
Dim month As Integer = CType(obj, Employee).BirthDate.Month
Dim day As Integer = CType(obj, Employee).BirthDate.Day
' porovnat měsíce
If month < Me.BirthDate.Month Then
Return 1
ElseIf month > Me.BirthDate.Month Then
Return -1
Else
' pokud jsou měsíce stejné, porovnat dny
Return Me.BirthDate.Day.CompareTo(day)
End If
End Function
End Class
Všimněte si hlavně řádku Implements IComparable, což je implementace rozhraní. Pomocí ní kolekci List říkáme, že objekty lze třídit a metodou CompareTo porovnáváme, který je větší a menší.
Metoda by měla porovnat objekt obj, který dostaneme v parametru, a instanci, na které se provádí, čili Me. Předaný objekt si přetypujeme na Employee a vytáhneme si z něj měsíc a den. Pokud má aktuální instance měsíc menší než objekt předaný, pak vrátíme 1, pokud menší, vrátíme –1. Pokud je měsíc stejný, musíme třídit podle dne. Tady si můžeme opět porovnání napsat, anebo použijeme standardní CompareTo na typ Integer a ten čísla porovná stejným způsobem, jako bychom to napsali my. Samozřejmě bychom mohli i měsíce třídit pomocí CompareTo, ale chtěl jsem ukázat obě možnosti. Lepší implementace je tedy tato:
''' <summary>
''' Vlastní způsob řazení
''' </summary>
Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
' vytáhnout měsíc a den
Dim month As Integer = CType(obj, Employee).BirthDate.Month
Dim day As Integer = CType(obj, Employee).BirthDate.Day
' setřídit podle měsíce
Dim i As Integer = Me.BirthDate.Month.CompareTo(month)
' pokud jsou měsíce stejné, třídit podle dne
If i = 0 Then i = Me.BirthDate.Day.CompareTo(day)
Return i
End Function
A ukázkový příklad použití? Naplníme si seznam List(Of Employee) testovacími daty a pak zavoláme na seznamu jednoduše Sort. Seznam si pomocí vestavěného mechanismu zjistí, jestli je datový typ tříditelný (implementuje IComparable) a pokud ano, tak jej setřídí. Pak jednoduše objekty vypíšeme popořadě na konzoli.
Module Module1
Sub Main()
'vstupní data
Dim employees As New List(Of Employee)
employees.Add(New Employee() With {.Name = "Lukáš", .BirthDate = #3/28/1978#})
employees.Add(New Employee() With {.Name = "Martin", .BirthDate = #5/4/1964#})
employees.Add(New Employee() With {.Name = "Tomáš", .BirthDate = #2/20/1959#})
employees.Add(New Employee() With {.Name = "Jan", .BirthDate = #8/18/1985#})
employees.Add(New Employee() With {.Name = "Lubomír", .BirthDate = #11/12/1986#})
employees.Add(New Employee() With {.Name = "Edmund", .BirthDate = #5/1/1973#})
employees.Add(New Employee() With {.Name = "Břetislav", .BirthDate = #1/14/1982#})
employees.Add(New Employee() With {.Name = "Jitka", .BirthDate = #7/19/1975#})
employees.Add(New Employee() With {.Name = "Jiří", .BirthDate = #12/17/1981#})
employees.Add(New Employee() With {.Name = "Luboš", .BirthDate = #6/1/1980#})
'seřadit je pomocí výchozího Compareru
employees.Sort()
'vypsat zaměstnance
For Each e In employees
Console.WriteLine(e.ToString())
Next
Console.ReadLine()
End Sub
End Module
Pokud chcete třídit objekty podle více různých kritérií (jednou podle posledního písmena z jména a jindy podle data narození), IComparable už nestačí. Stejně tak není možné použít ho v případě, kdy chceme třídit vestavěné .NET Frameworkové objekty (třeba FileInfo), do kterých si metody nemůžeme jen tak přidávat.
Pak je vhodné pro každý typ třídění napsat vlastní třídu a implementovat rozhraní IComparer. Pak můžete do každé z nich napsat libovolnou metodu Compare. Pak při volání metody Sort předáte jako druhý parametr novou instanci této třídy. To poskytuje lepší možnosti třídění, můžete například třídě přidat pár vlastností a během třídění je vzít v úvahu. Zcela jistě to zvládnete sami.
Jinak IComparable i IComparer mají i generické varianty, které můžete použít rovněž a nebudete musetpřetypovávat parametry z typu Object.