基於業務對象(列表)的排序

來源:互聯網
上載者:User
文章目錄
  • IComparable<T>介面
  • IComparer<T> 介面
  • 實現 IComparer<T>介面
  • 頁面調用
代碼下載:http://www.tracefact.net/sourcecode/filterSorting.rar

基於業務對象的排序引言

在上一篇文章 基於業務對象的篩選 中,我們討論了如何?Predicate<T>(T object)委託,自訂DateFilter 類來對業務對象進行篩選。與篩選一樣,排序也是常見且重要的操作。在對業務對象進行排序時,不能使用ObjectDataSource作為資料來源,因為它只對 DataView、DataTable 和 DataSet 支援自動排序。但你仍可以對GridView編寫Sorting事件的處理方法,通過拼裝SQL語句,使用“Order By”子句來完成排序。

和進行篩選的思路一樣,如果我們將業務對象緩衝在伺服器上,第一次訪問時從資料庫提取資料,然後進行緩衝,後繼的請求只針對緩衝了的業務對象進行,則可以降低對資料庫的依賴,提高效率。本文將討論如何對擷取的業務對象進行排序,包括簡單排序、任意列排序、以及多列複合排序。

NOTE:本文是接著上一篇寫的,一些重複的內容本文將不再講述,建議先閱讀 基於業務對象的篩選 。

簡單排序 - 對固定屬性的預設排序

與上篇文章不同,我不再說明使用拼裝SQL來完成排序的方式,我們直接看基於List<Order>對象的排序。我們知道List<T>提供了Sort()方法來進行排序操作,那麼它又如何使用呢?我們先建立一個ObjSort.aspx檔案,然後在代碼後置中添加如下代碼:

protected void Page_Load(object sender, EventArgs e)
 {
     Label lb1 = new Label();

     List<int> list = new List<int>();
     list.Add(4);
     list.Add(5);
     list.Add(2);
     list.Add(9);
     list.Add(1);

     foreach (int item in list) {
        lb1.Text += item.ToString() + ", ";
     }

     form1.Controls.Add(lb1);
     HtmlGenericControl hr = new HtmlGenericControl("hr");
     form1.Controls.Add(hr);

     Label lb2 = new Label();
     list.Sort();     // 對列表進行排序
     foreach (int item in list) {
        lb2.Text += item.ToString() + ", ";
     }
     form1.Controls.Add(lb2);
 }

可以看到,通過在List<int>上使用Sort()方法,對列表中的元素進行了排序。現在我們在OrderManager.cs中新添一個方法GetSortList(),它用於擷取列表對象,因為GetList()方法返回的記錄數太多,而在本文中我們僅關注排序,所以我們僅返回15條記錄。

// 擷取用於排序的列表
public static List<Order> GetSortList() {

    List<Order> list = HttpContext.Current.Cache["sortList"] as List<Order>;

    if (list == null) {
       list = GetList("Select Top 15 OrderId, CustomerId, ShipCountry, OrderDate From Orders");
       HttpContext.Current.Cache.Insert("sortList", list);
    }

    return list;
}

如果你沒有看上一篇文章,那麼只要知道這個方法返回一個List<Order>類型的業務對象,代表一個訂單列表就可以了(Order對象包含四個公用屬性,分別是OrderId, CustomerId, OrderDate, Country)。然後我們建立 ObjSort2.aspx檔案,在它上面拖放一個Reperter控制項,並編寫一些代碼,用於顯示一個表格:

<asp:Repeater runat="server" ID="rpOrderList" >
    <HeaderTemplate>
     <table>
        <tr>
            <th>
               <asp:LinkButton ID="lbtOrderId" runat="server">OrderId</asp:LinkButton>
            </th>
            <th>
               <asp:LinkButton ID="lbtCustomerId" runat="server">CustomerId</asp:LinkButton>
            </th>
            <th>
               <asp:LinkButton ID="lbtOrderDate" runat="server">OrderDate</asp:LinkButton>
            </th>
             <th>
                <asp:LinkButton ID="lbtCountry" runat="server">Country</asp:LinkButton>
            </th>
        </tr>
    </HeaderTemplate>
    <ItemTemplate>
        <tr>
            <td><%#Eval("OrderId") %></td>
            <td><%#Eval("CustomerId") %></td>
            <td><%#Eval("OrderDate") %></td>
             <td><%#Eval("Country") %></td>
        </tr>
    </ItemTemplate>      
    <FooterTemplate>     
     </table>
    </FooterTemplate>    
 </asp:Repeater>

然後,我們在後置代碼ObjSort2.aspx.cs的Page_Load事件中,添加這樣兩行語句:

rpOrderList.DataSource = OrderManager.GetSortList();
rpOrderList.DataBind();

然後再開啟頁面,可以看到在頁面上輸出了列表。現在我們想對這個列表進行排序,那麼我們仿照List<int>的做法,修改上面的代碼:

List<Order> list = OrderManager.GetSortList();
list.Sort();      // 期望可以進行排序
rpOrderList.DataSource = list;
rpOrderList.DataBind();

實際上,我們會得到錯誤:必須至少有一個對象實現 IComparable。

IComparable<T>介面

我們就是自己想也應該想到為什麼會出錯:Order對象包含了四個屬性OrderId、CustomerId、OrderDate、Country,而int只有它本身的值,所以,當我們在List<Order>上調用Sort()的時候,列表對象根本不知道應該如何排序,也不知道以哪個屬性來進行排序。而IComparable介面,定義了如何進行排序的規則,如果我們想要對List<Order>對象進行排序,那麼我們就需要讓列表的元素,也就是Order對象實現這個介面。實際上,List<int>之所以可以直接調用Sort()方法,是因為int,以及幾乎全部的基本類型(比如string,char,datetime等),本身就實現了IComparable<T>。

public interface IComparable<T> {
    int CompareTo(T other);
}

這個介面只需要實現一個方法,CompareTo(),它傳遞與要比較的對象(列表中的當前對象)同類型的另一個對象 other,返回一個int類型的值:小於零 當前對象小於 other 參數。零 此對象等於 other。大於零 當前對象大於 other。

現在我們讓Order對象(Order參見下載的代碼)實現這個介面:

// 實現 IComparable<T> 介面
public int CompareTo(Order other) {
    return this.CustomerId.CompareTo(other.CustomerId);
}

我們將排序的規則委託給了CustomerId去處理,因為CustomerId是一個string類型,調用了它的CompareTo()方法。這樣,在List<Order>上調用Sort()的時候就會依據這裡定義的規則,以CustomerId進行排序了。再次開啟ObjSort.aspx,應該可以看到列表按CustomerId進行了排序。

進階排序 - 多個屬性群組合排序IComparer<T> 介面

上面僅僅是為列表提供了一個預設排序,實際上,我們經常要求對多個列進行排序,我們還會要求按降序或者升序進行排序,我們甚至會要求對多個列的組合進行排序,比如:先對CustomerId進行升序排列,再對OrderDate降序排列。此時雖然使用CompareTo(Order other)也可以實現,但是要給Order對象添加額外的欄位或者屬性,這些.Net Framewok已經考慮到了,並提供了IComparer<T>介面封裝了定序,我們可以通過實現這個介面來完成排序。

public interface IComparer<T> {
    int Compare(T x, T y);
}

IComparer<T>只需要實現一個方法,Compare()它接受兩個同一類型的參數,並返回int類型的結果,與IComparable<T>類似,當傳回值小於0時,x小於y;等於0時,x等於y;大於0時,x大於y。需要注意的是:這個介面不是要求我們讓Order對象實現它,而是要求另外一個對象實現它,比如OrderComparer,而在調用Sort()方法時,將它作為參數傳遞進去。因為這個OrderComparer只是用於對Order對象進行排序,不能應用於其他對象,所以我們將它聲明為Order的嵌套類。

實現 IComparer<T>介面

開啟Order.cs檔案,對它進行如下修改,先添加一個枚舉SortDirection,用於表示排序的方向:

// 可複用的枚舉,表示排序的方向
public enum SortDirection {
    Ascending = 0,
    Descending
}

在Order類的內部,添加一個枚舉,這個枚舉類型代表了可以進行排序的屬性:

// 嵌套枚舉,僅應用於此業務對象,可排序的屬性
public enum SortField {
    OrderId,
    CustomerId,
    OrderDate,
    Country
}

我們還需要再定義一個結構Sorter,這個結構包含兩個欄位,一個SortDirection類型,一個SortField類型,它封裝了排序的必要資訊:對於哪個屬性按照哪種方式(升序或降序)排序。由於這個結構依然是只針對Order對象的,所以我們還是把它定義在Order內部:

// 嵌套結構,僅應用於此業務對象,排序的屬性和方式
public struct Sorter {
    public SortField field;
    public SortDirection direction;

    public Sorter(SortField field, SortDirection direction) {
       this.field = field;
       this.direction = direction;
    }

    public Sorter(SortField field) {
       this.field = field;
       this.direction = SortDirection.Ascending;
    }
}

接著,我們在Order內部定義實現IComparer<T>的類OrderComparer:

// 嵌套類,僅對於此業務對象進行排序
public class OrderComparer : IComparer<Order> {

}

現在考慮如何?它:因為我們要實現對某個屬性,按某種方式排序,那麼我們至少要將這兩個參數傳進去,所以OrderCompare應該包含欄位用於維護SortDirection和SortField;因為我們期望可以對多個屬性群組合排序,所以應該維護一個它們的列表,而SortDirection和SortFiled,已經包含在了Sorter結構中,所以它只要維護一個List<Sorter>結構就可以了:

public class OrderComparer : IComparer<Order> {
    private List<Sorter> list;
    // 建構函式,設定排序欄位列表
    public OrderComparer(List<Sorter> list) {
        this.list = list;
    }
}

接著考慮如何排序,先從簡單入手,我們不考慮對於多個屬性的排序,只對某個屬性按某種方式排序,那麼我們需要添加一個方法CompareTo(),它接受排序的屬性、排序的方式,以及排序的兩個對象,最後返回int類型,說明這兩個對象的大小(位置的先後):

// 對單個屬性按某種方式進行排序
public int Compare(Order x, Order y, SortField field, SortDirection direction) {
    int result = 0;          // 預設排序位置不變化

    switch (field) {
       case SortField.Country:
           if (direction == SortDirection.Ascending)
              result = x.Country.CompareTo(y.Country);
           else
              result = y.Country.CompareTo(x.Country);
           break;
       case SortField.CustomerId:
           if (direction == SortDirection.Ascending)
              result = x.CustomerId.CompareTo(y.CustomerId);
           else
              result = y.CustomerId.CompareTo(x.CustomerId);
           break;
       case SortField.OrderDate:
           if (direction == SortDirection.Ascending)
              result = x.OrderDate.CompareTo(y.OrderDate);
           else
              result = y.OrderDate.CompareTo(x.OrderDate);
           break;
       case SortField.OrderId:
           if (direction == SortDirection.Ascending)
              result = x.OrderId.CompareTo(y.OrderId);
           else
              result = y.OrderId.CompareTo(x.OrderId);
           break;
    }

    return result;
}

但是這個方法不會實現IComparer<T>介面,也沒有辦法進行多個列的排序。繼續進行之前,我們考慮下如何對兩個對象的多個屬性(比如A、B、C)來進行排序:先對屬性A進行比較,如果屬性A相同,繼續比較屬性B,如果屬性B相同,繼續比較屬性C。在這個過程中,只要有任意一個屬性不相同,就可以決定兩個對象的先後順序,也就是不再進行後面屬性的比較。

有了思路,我們現在實現IComparer<T>介面,編寫方法

// 實現 IComparer介面
public int Compare(Order x, Order y) {
    int result = 0;
    foreach (Sorter item in list) {
       result = Compare(x, y, item.field, item.direction);
       if (result != 0)      // 一旦result不為0,則已經區分出位置大小,跳出迴圈
           break;
    }

    return result;
}

在這個方法中,我們遍曆了List<Sorter>,並且在foreach語句中調用了我們前面定義的對單個屬性的比較方法Compare(Order x, Order y, SortField field, SortDirection direction),一旦比較結果不為0,那麼就跳出迴圈。

好了OrderComparer類的實現已經完成了,我們再看下還有什麼可以完善的地方:如果以後每次調用Sort進行排序的時候,都要先需要先建立列表,指定定序,構造OrderCompare對象,顯然會很麻煩,所以我們給在Book類中添加一組重載了的方法GetComparer(),用來簡化以後調用時的操作步驟:

// 指定排序屬性 和 排序方式
public static OrderComparer GetComparer(SortField field, SortDirection direction) {
    List<Sorter> list = new List<Sorter>();
    Sorter sorter = new Sorter(field, direction);
    list.Add(sorter);
    return new OrderComparer(list);
}

// 指定排序屬性,預設為升序
public static OrderComparer GetComparer(SortField field) {
    return new OrderComparer(field, SortDirection.Ascending);
}

// 預設為以OrderId升序排列
public static OrderComparer GetComparer() {
    return new OrderComparer(SortField.OrderId, SortDirection.Ascending);
}

// 排序列表
public static OrderComparer GetComparer(List<Sorter> list) {
    return new OrderComparer(list);
}

好了,現在OrderComparer類就全部建立好了,我們接下來看一下如何使用它:

頁面調用

我們修改一下代碼後置檔案,來看下如何進行設定,我們將Sort()改成這樣:

list.Sort(Order.GetComparer(Order.SortField.Country, SortDirection.Descending)); // 以Country倒序排列

然後查看頁面,發現列表以Country屬性進行了倒序排列。那如果我們想先按Country倒序排列,再按CustomerId順序排列,又該如何呢?

// 以Country降序, CustomerId升序排列
List<Order.Sorter> sorterList = new List<Order.Sorter>(); //先建立sorterList
sorterList.Add(new Order.Sorter(Order.SortField.Country, SortDirection.Descending));     // 先以Country Desc排序
sorterList.Add(new Order.Sorter(Order.SortField.CustomerId));//再以CustomerId Asc
list.Sort(Order.GetComparer(sorterList));

現在開啟頁面,可以看到列表以我們期望的方式進行了排序。在本文中,由於僅僅是出於示範的目的,所以我們在代碼中直接書寫了用於排序的SortList,實際上這些應該是基於使用者選擇而動態建立的。在ObjSort2.aspx頁面上,表格的標題我使用了LinkButton,有興趣的話可以編寫LinkButton的Click事件,來動態地實現這一排序過程。

總結

本文詳細的討論了如何對列表(業務對象)進行排序。

我們首先瞭解IComparable<T>介面,學習了如何?這個介面以實現針對某一欄位的一個預設排序。接著,我們詳細地討論了如何通過實現一個IComparer<T>介面,來實現可以對任意單個屬性以及多個屬性群組合的排序。

大家可以看到,一旦掌握了方法以後,再編寫諸如OrderComparer這樣的代碼是枯燥無味的,以後我們再一起看看如果利用反射來編寫一個小程式為我們自動地產生這些代碼。

感謝閱讀,希望這篇文章能給你帶來協助。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.