C# 中的List.Sort()–集合排序方法全面解析
在C#中,List.Sort() 不僅為我們提供瞭默認的排序方法,還為我們提供瞭4種自定義排序的方法,通過默認排序方法,我們無需重寫任何Sort()方法的實現代碼,就能對單參數類型的List數據進行單一規則的排序,如果通過對這些方法進行改進我們可以輕松做到對多參數、多規則的復雜排序。
下面是C#自定義排序的4種方法:
List<T>.Sort(); List<T>.Sort(IComparer<T> Comparer); List<T>.Sort(int index, int count, IComparer<T> Comparer); List<T>.Sort(Comparison<T> comparison);
實現目標
假設存在一個People類,包含Name、Age屬性,在客戶端中創建List保存多個實例,希望對List中的內容根據Name和Age參數進行排序,排序規則為,先按姓名升序排序,如果姓名相同再按年齡的升序排序:
class People { public People(string name, int age) { Name = name; Age = age; } public string Name { get; set; } //姓名 public int Age { get; set; } //年齡 } // 客戶端 class Client { static void Main(string[] args) { List<People> peopleList = new List<People>(); peopleList.Add(new People("張三", 22)); peopleList.Add(new People("張三", 24)); peopleList.Add(new People("李四", 18)); peopleList.Add(new People("王五", 16)); peopleList.Add(new People("王五", 30)); } }
方法一、對People類繼承IComparable接口,實現CompareTo()方法
該方法為系統默認的方法,單一參數時會默認進行升序排序。但遇到多參數(Name、Age)排序時,我們需要對該默認方法進行修改。
方法一:People類繼承IComparable接口,實現CompareTo()方法
IComparable<T>:定義由值類型或類實現的通用比較方法,旨在創建特定於類型的比較方法以對實例進行排序。
原理:自行實現的CompareTo()方法會在list.Sort()內部進行元素兩兩比較,最終實現排序
class People : IComparable<People> { public People(string name, int age) { Name = name;Age = age; } public string Name { get; set; } public int Age { get; set; } // list.Sort()時會根據該CompareTo()進行自定義比較 public int CompareTo(People other) { if (this.Name != other.Name) { return this.Name.CompareTo(other.Name); } else if (this.Age != other.Age) { return this.Age.CompareTo(other.Age); } else return 0; } } // 客戶端 peopleList.Sort(); // OUTPUT: // 李四 18 // 王五 16 // 王五 30 // 張三 22 // 張三 24
方法二:增加People類的外部比較類,繼承IComparer接口、實現Compare()方法
區別於上述繼承IComparable的方法,該方法不可在People內繼承實現IComparer接口,而是需要新建比較方法類進行接口實現
方法二:新建PeopleComparer類、繼承IComparer接口、實現Compare()方法
原理:list.Sort()將PeopleComparer類的實例作為參數,在內部使用Compare()方法進行兩兩比較,最終實現排序(註:上述方法為CompareTo(),此處為Compare()方法)
// 自定義比較方法類 class PeopleComparer : IComparer<People> { // 區別於CompareTo()單參數,此處為雙參數 public int Compare(People x, People y) { if (x.Name != y.Name) { return x.Name.CompareTo(y.Name); } else if (x.Age != y.Age) { return x.Age.CompareTo(y.Age); } else return 0; } } // 客戶端 // 傳入參數為自定義比較類的實例 peopleList.Sort(new PeopleComparer()); // OUTPUT: // 李四 18 // 王五 16 // 王五 30 // 張三 22 // 張三 24
同理,List<T>.Sort(int index, int count, IComparer<T> Comparer) 方法的參數:待排元素起始索引、待排元素個數、排序方法
方法三、采用泛型委托 Comparison<T>,綁定自定義的比較方法
區別於上述繼承接口的方法,此方法的參數為 泛型委托 Comparison<T>
委托原型:public delegate int Comparison<in T>(T x, T y);
方法三:依照委托的使用方法,首先創建委托實例MyComparison,並綁定到自定義的比較方法PeopleComparison()上,最終調用list.Sort()時 將委托實例傳入
原理:list.Sort()根據傳入的委托方法,進行兩兩元素比較最終實現排序
// 客戶端 class Client { // 方法0 自定義比較方法 public static int PeopleComparison(People p1, People p2) { if (p1.Name != p2.Name) { return p1.Name.CompareTo(p2.Name); } else if (p1.Age != p2.Age) { return p1.Age.CompareTo(p2.Age); } else return 0; } static void Main(string[] args) { / 創建list ... / // 方法0 創建委托實例並綁定 Comparison<People> MyComparison = PeopleComparison; // 傳入該實例實現比較方法 peopleList.Sort(MyComparison); // OUTPUT: // 李四 18 // 王五 16 // 王五 30 // 張三 22 // 張三 24 } }
此外,既然Comparison<T>是泛型委托,則完全可以用 Lambda表達式 進行描述:
// Lambda表達式實現Comparison委托 peopleList.Sort((p1, p2) => { if (p1.Name != p2.Name) { return p2.Name.CompareTo(p1.Name); } else if (p1.Age != p2.Age) { return p2.Age.CompareTo(p1.Age); } else return 0; }); // OUTPUT: // 張三 24 // 張三 22 // 王五 30 // 王五 16 // 李四 18
總結
雖然本文僅使用瞭List<T>一種容器對Sort()方法進行闡述,但是不同容器的使用Sort()的方法大相徑庭,因為核心的原理都是應用兩種接口及泛型委托:
兩種接口:IComparable<T> 、 IComparer<T>
泛型委托:Comparison<T>
參考
IComparable接口 – Microsoft
Comparison委托 – Microsoft
IComparer接口 – Microsoft
附:一個完整的測試Demo
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ListSort { class Program { static void DisplayInfo<T>(List<T> list) { //輸出List元素內容 foreach(var item in list) { System.Console.Write("{0} ",item.ToString()); } System.Console.WriteLine(""); } // 方法3 自定義委托泛型比較方法 public static int PeopleComparison(People p1, People p2) { if (p1.Name != p2.Name) { return p1.Name.CompareTo(p2.Name); } else if (p1.Age != p2.Age) { return p1.Age.CompareTo(p2.Age); } else return 0; } static void Main(string[] args) { List<People> peopleList = new List<People>(); peopleList.Add(new People("張三", 22)); peopleList.Add(new People("張三", 24)); peopleList.Add(new People("李四", 18)); peopleList.Add(new People("王五", 16)); peopleList.Add(new People("王五", 30)); System.Console.WriteLine("排序前原始數據:"); DisplayInfo(peopleList); System.Console.WriteLine("------------------------------------"); System.Console.WriteLine("方法1排序後數據:"); peopleList.Sort(); DisplayInfo(peopleList); System.Console.WriteLine("方法2排序後數據:"); DisplayInfo(peopleList); // 方法1 使用IComparer<T>接口。 peopleList.Sort(new PeopleComparer()); // 方法2 除以上兩種方法以外還可以使用另一種方法,在People類中實現IComparable<T> peopleList.Sort(); System.Console.WriteLine("方法3排序後數據:"); DisplayInfo(peopleList); // 方法3 創建泛型委托實例並綁定 Comparison<People> MyComparison = PeopleComparison; // 傳入該實例實現比較方法 peopleList.Sort(MyComparison); System.Console.WriteLine("方法3排序後數據:"); DisplayInfo(peopleList); // 方法3 使用Comparison<T>委托,Lambda寫法 peopleList.Sort((left, right) => { //先按姓名排序,如果姓名相同再按年齡排序 int x = left.Name.CompareTo(right.Name); if(x==0) { if (left.Age > right.Age) x = 1; else if (left.Age == right.Age) x = 0; else x = -1; } return x; }); } } //方法一 public class People : IComparable<People> { public int Age { get;set;} public string Name { get;set;} public People(string name,int age) { this.Name = name; this.Age = age; } public override string ToString() { string result = ""; result = "["+this.Name+","+ this.Age.ToString()+"]"; return result; } public int CompareTo(People other) { int x = this.Name.CompareTo(other.Name); if(x==0) { if (this.Age > other.Age) x = 1; else if (this.Age == other.Age) x = 0; else x = -1; } return x; } } //方法二 public class PeopleComparer : IComparer<People> { public int Compare(People left, People right) { int x = left.Name.CompareTo(right.Name); if(x==0) { if (left.Age > right.Age) x = 1; else if (left.Age == right.Age) x = 0; else x = -1; } return x; } } }
補充:C# IComparable和IComparer接口和自定義比較器
前言
ArrayList裡面有一個方法:
public virtual void Sort(IComparer comparer);
使用指定的比較器對整個 System.Collections.ArrayList 中的元素進行排序。
comparer:比較元素時要使用的 System.Collections.IComparer 實現。
啥玩意啊?
正文
1.Comparer類簡單介紹
想弄清楚這個,我們先來看看這麼一個類。
在System.Collections名稱空間中,有這麼一個類:Comparer。顧名思義,他可以實現對簡單類型的比較,什麼意思呢?來看如下代碼:
int a=1,b=2;
正常情況下,我們要怎樣比較他們的大小?if,運算符,……?這當然可以,不過Comparer已經給我們提供瞭一個函數,可以直接使用:(需要using System.Collections;)
Console.WriteLine(Comparer.Default.Compare(a,b));
因為a<b,所以控制臺會輸出-1。(這個函數總是返回-1,0,1三個值。)
這裡通過Comparer裡的靜態屬性Default獲得Comparer的實例調用瞭Comparer裡的非靜態函數Compare。
(還可以比較根據字母比較兩個string類型,這裡就省略介紹瞭)
2.自定義比較器,IComparable,IComparer接口
當然,這個類不僅僅隻是用來比較兩個數的大小的。有時候我們想直接比較兩個對象,但是引用裡面的屬性或許比較麻煩。尤其是參考要素過多,不好直接比較的時候,怎樣才能更高效地比較兩個對象呢?這時候,我們就需要自定義比較器瞭。
首先來介紹IComparable接口。這個接口裡隻有一個方法CompareTo()。讓你的類實現這個接口的CompareTo方法,就可以直接調用這個方法和另一個對象比較。下面是例子:
public class ClassTest : IComparable { public int intTest; public int CompareTo(object obj) { return intTest-((ClassTest)obj).intTest; //這裡的代碼可以按需要自己編寫,這裡隻是一個簡單的示例 } }
然後就可以直接使用啦:
ClassTest a = new ClassTest(){intTest=1}; ClassTest b = new ClassTest(){intTest=2}; Console.WriteLine(a.CompareTo(b));//輸出-1 Comparer類已經為我們提供瞭IComparer的默認實現,但我們仍然可以自定義它。新建一個類:(記得using System.Collections;) public class ClassTestComparer : IComparer { public static IComparer Default = new ClassTestComparer(); //這裡必須使用這樣的定義,將對象轉化為IComparer類型有很大用處,下面會介紹 public int Compare(object a,object b) { return ((ClassTest)a).intTest - ((ClassTest)b).intTest; //同樣這裡使用最簡單的示例,但是你可以大放異彩 } }
註意,如果用於比較的類和設定的類不一樣,就會出現錯誤。
使用示例:
ClassTest a = new ClassTest(){intTest=1}; ClassTest b = new ClassTest(){intTest=2}; Console.WriteLine(ClassTestComparer.Default.Compare(a,b)); //結果是-1
可以發現,這兩個接口的不同之處在於:IComparable在要比較的對象的類中實現,可以比較該對象和另一個對象。IComparer在一個單獨的類中實現,可以比較任意兩個對象(關鍵是你的設置)。
3.對集合排序
當然,這兩個接口還有更強大的用處。我們可以使用這兩個接口對集合進行排序。還記得前言裡的Sort()方法嗎?接下來就以ArrayList為例,介紹如何使用。
ArrayList ClassTests = new ArrayList(); ClassTest a = new ClassTest(){intTest=1}; ClassTest b = new ClassTest(){intTest=2}; ClassTest c = new ClassTest(){intTest=3}; ClassTests.Add(a); ClassTests.Add(b); ClassTests.Add(c); ClassTests.Sort(); //使用無參的Sort,將調用類中的CompareTo()方法,因為ClassTest實現瞭這個方法,所以是可以調用的。如果沒有實現,編譯器會報錯。 ClassTests.Sort(ClassTestComparer.Default); //這將使用Compare()方法對集合中的元素排序。ClassTestComparer類實現瞭這個方法,並且提供瞭一個IComparer類型的屬性。
需要註意的是:
兩個接口提供的方法返回值都是int類型的,負數代表小於,0代表等於,正數代表大於。所以對數字之外的自定義比較器,需要人工設定什麼是“大”,什麼是“小”。所以上文示例中兩個數直接相減,就可以比較大小。
排序完之後,按照返回的int值,集合是由小到大排列的。
使用無參Sort()時,集合中至少要有一個類實現瞭IComparable,否則會報錯。
一般來說,都是對同一個類進行比較。不過,也可以實現對不同類比較的代碼,這就看具體需要瞭。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。