針對某些特定的用途, 我們時常有自訂類別的需要。不過如果我們把自訂類別加入到陣列之中時, 我們要怎麼讓這個陣列可以排序呢...
針對某些特定的用途, 我們時常有自訂類別的需要。不過如果我們把自訂類別加入到陣列之中時, 我們要怎麼讓這個陣列可以排序呢?
舉個例子, 我為排課的需要, 自訂了一個 classes 自訂類別:
public class classes {
public DateTime time;
public string name;
public string instructor;
...
}
現在我在程式中建立了一個內含三個元素的 classes 類別的陣列:
classes[] myClasses = new classes[3];
然後, 我在 myClasses 陣列中加上了許多實體物件。但是我突然覺得我需要讓 myClasses 依照時間來排序, 該怎麼做?
當然, 你或許會發現有 Array.Sort 方法可以選擇, 但是你就沒辦法使用, 因為你還必須在自訂類別中加上一些東西。關鍵點在於, 你必須在這個自訂類別中實做 IComparable 介面, 才能夠提供陣列及集合結構的排序功能。這個介面可以說是最容易實做的介面之一了, 因為你只需要在這個自訂類別中加上一個 CompareTo 方法即可, 範例如下:
VB -
Public Class classes
Implements IComparablePublic time As DateTime
Public name As String
Public instructor As StringPublic Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
Dim tobeCompared As classes = CType(obj, classes)
If Me.time > tobeCompared.time Then
Return 1
ElseIf Me.time = tobeCompared.time Then
Return 0
Else
Return -1
End IfEnd Function
End Class
C# -
public class classes : IComparable
{
public DateTime time;
public string name;
public string instructor;
public int CompareTo(object obj)
{
classes tobeCompared = (classes)obj;
if (this.time > tobeCompared.time)
return 1;
else if (this.time == tobeCompared.time)
return 0;
else return -1;
}
}
加上這個方法之後, 你就會發現你已經可以使用 Array.Sort() 來排序了! 如下範例:
VB -
Dim weekClass As classes() = New classes() {New classes, New classes, New classes}
weekClass(0).time = Now.AddDays(0)
weekClass(0).name = "Day 1"
weekClass(1).time = Now.AddDays(-1)
weekClass(1).name = "Day 0"
weekClass(2).time = Now.AddDays(1)
weekClass(2).name = "Day 2"
Array.Sort(weekClass)
C# -
classes[] weekClass = new classes[3] {new classes(), new classes(), new classes() };
weekClass[0].time = DateTime.Now.AddDays(0);
weekClass[0].name = "Day 1";
weekClass[1].time = DateTime.Now.AddDays(-1);
weekClass[1].name = "Day 0";
weekClass[2].time = DateTime.Now.AddDays(1);
weekClass[2].name = "Day 2";
Array.Sort(weekClass);
實作 IComparable 介面之後, 你除了立即可以提供陣列的排序功能之外, 也自動提供各集合結構的排序。例如以下範例:
VB -
Dim monClass As List(Of classes) = New List(Of classes)
Dim c1 As New classes
c1.time = Now.AddDays(0)
c1.name = "Day 1"
monClass.Add(c1)
Dim c2 As New classes
c2.time = Now.AddDays(-1)
c2.name = "Day 0"
monClass.Add(c2)
Dim c3 As New classes
c3.time = Now.AddDays(1)
c3.name = "Day 2"
monClass.Add(c3)
monClass.Sort()
C# -
List<classes> monClass = new List<classes>();
classes c1 = new classes();
c1.time = DateTime.Now.AddDays(0);
c1.name = "Day 1";
monClass.Add(c1);
classes c2 = new myClass();
c2.time = DateTime.Now.AddDays(-1);
c2.name = "Day 0";
monClass.Add(c2);
classes c3 = new myClass();
c3.name = "Day 2";
c3.time = DateTime.Now.AddDays(1);
monClass.Add(c3);
monClass.Sort();
現在你的類別陣列和集合物件已經可以被拿來排序了, 但是既然可以排序, 自然也就具有搜尋的能力, 你可以使用 List 物件的 BinarySearch() 方法來找出特定的物件。在上面的範例中, 只要 classes.time 符合就算有找到; 但如果你希望再嚴謹些, 例如必須 classes.time、classes.name 及其它欄位的值都符合才算數, 那麼你可以再修改類別中的 CompareTo() 方法。
不過我後來又去查了 MSDN 上關於 Array.Sort 和 Array.BinarySearch 的說明, 發現它們其實註明必須實作 IComparer 介面 (雖然我們在上例中僅實作 IComparable 也可以)。為求嚴謹, 我們也把 IComparer 實作了, 請看以下範例:
public class classes : IComparable, Collections.IComparer<object>
{
public DateTime time;
public string name;
public string instructor;
public int CompareTo(object obj)
{
return Compare(tobeCompared, this);
}
public int Compare(object obj1, object obj2)
{
classes obeCompared = (classes)obj1;
classes thisIns = (classes)obj2;
if (thisIns > tobeCompared.time)
return 1;
else if (thisIns.time == tobeCompared.time)
return 0;
else return -1;
}
}
IComparer 介面會要求實作 Compare 方法。為求精簡, 我在以上程式中讓 CompareTo 去呼叫 Compare 方法, 所以主要的邏輯都改放在 Compare 方法中。還有一點請注意, 我們可以同時實作 IComparable 和 IComparable<object> 兩個介面, 可是我發現似乎只要實作前者即可 (況且兩者都同時實作 CompareTo 方法。而 IComparer 介面都只能寫作 IComparer<object>, 它似乎並沒有非泛型的型式。
最後再補充一個範例。如果你還要讓自訂型別的物件可以比較是否相等, 你可以實作 IEqualityComparer; 以下是一個完整的例子:
public class myType : IComparable, IComparer<object>, IEqualityComparer<object>
{
// Properties
public DateTime startTime { get; set; }
...public myType() { }
public int CompareTo(object obj)
{
myType tobeCompared = (myType)obj;
return Compare(tobeCompared, this);
}/// <summary>
/// 預設的 Comparer, 比對標的是 myType.startTime
/// </summary>
public int Compare(object obj1, object obj2)
{
myType tobeCompared = (myType)obj1;
myType compare2 = (myType)obj2;
if (compare2.startTime > tobeCompared.startTime)
return 1;
else if (compare2.startTime == tobeCompared.startTime)
return 0;
else return -1;
}/// <summary>
/// 預設的 Equals 比對函式, 比對標的是 myType.startTime 加上 myType.title
/// </summary>
public new bool Equals(object obj1, object obj2)
{
myType tobeCompared = (myType)obj1;
myType compare2 = (myType)obj2;
if ((compare2.startTime == tobeCompared.startTime) &&
(compare2.title.Trim().ToLower() == tobeCompared.title.Trim().ToLower()))
return true;
else
return false;
}public int GetHashCode(object obj)
{
return InternalGetHashCode(this);
}// 呼叫作業系統提供的 InternalGetHashCode 函式
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.InternalCall)]
internal static extern int InternalGetHashCode(object obj);
}
在這個範例中, 當我們實作了 IComparable 之後就可以在一般陣列或 List<myType> 陣列中進行排序。至於實作 IEqualityComparer 的主要目的是提供在 LINQ 運算式比較兩個物件是否相等的運算, 如此像 LINQ 算式中的 DISTINCT 等指令才有作用。關於這方面的細節, 我們未來有機會再詳述。