讓自訂類別的陣列物件具有排序與搜尋的能力

針對某些特定的用途, 我們時常有自訂類別的需要。不過如果我們把自訂類別加入到陣列之中時, 我們要怎麼讓這個陣列可以排序呢...

 

針對某些特定的用途, 我們時常有自訂類別的需要。不過如果我們把自訂類別加入到陣列之中時, 我們要怎麼讓這個陣列可以排序呢?

舉個例子, 我為排課的需要, 自訂了一個 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 IComparable

    Public time As DateTime
    Public name As String
    Public instructor As String

    Public 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 If

    End 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 等指令才有作用。關於這方面的細節, 我們未來有機會再詳述。

 


Dev 2Share @ 點部落