C# 中的List.Sort()--集合排序方法全面解析
在C#中,List.Sort() 不僅為我們提供了默認(rèn)的排序方法,還為我們提供了4種自定義排序的方法,通過默認(rèn)排序方法,我們無需重寫任何Sort()方法的實(shí)現(xiàn)代碼,就能對(duì)單參數(shù)類型的List數(shù)據(jù)進(jìn)行單一規(guī)則的排序,如果通過對(duì)這些方法進(jìn)行改進(jìn)我們可以輕松做到對(duì)多參數(shù)、多規(guī)則的復(fù)雜排序。
下面是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);
實(shí)現(xiàn)目標(biāo)
假設(shè)存在一個(gè)People類,包含Name、Age屬性,在客戶端中創(chuàng)建List保存多個(gè)實(shí)例,希望對(duì)List中的內(nèi)容根據(jù)Name和Age參數(shù)進(jìn)行排序,排序規(guī)則為,先按姓名升序排序,如果姓名相同再按年齡的升序排序:
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));
}
}
方法一、對(duì)People類繼承IComparable接口,實(shí)現(xiàn)CompareTo()方法
該方法為系統(tǒng)默認(rèn)的方法,單一參數(shù)時(shí)會(huì)默認(rèn)進(jìn)行升序排序。但遇到多參數(shù)(Name、Age)排序時(shí),我們需要對(duì)該默認(rèn)方法進(jìn)行修改。
方法一:People類繼承IComparable接口,實(shí)現(xiàn)CompareTo()方法
IComparable<T>:定義由值類型或類實(shí)現(xiàn)的通用比較方法,旨在創(chuàng)建特定于類型的比較方法以對(duì)實(shí)例進(jìn)行排序。
原理:自行實(shí)現(xiàn)的CompareTo()方法會(huì)在list.Sort()內(nèi)部進(jìn)行元素兩兩比較,最終實(shí)現(xiàn)排序
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()時(shí)會(huì)根據(jù)該CompareTo()進(jìn)行自定義比較
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接口、實(shí)現(xiàn)Compare()方法
區(qū)別于上述繼承IComparable的方法,該方法不可在People內(nèi)繼承實(shí)現(xiàn)IComparer接口,而是需要新建比較方法類進(jìn)行接口實(shí)現(xiàn)
方法二:新建PeopleComparer類、繼承IComparer接口、實(shí)現(xiàn)Compare()方法
原理:list.Sort()將PeopleComparer類的實(shí)例作為參數(shù),在內(nèi)部使用Compare()方法進(jìn)行兩兩比較,最終實(shí)現(xiàn)排序(注:上述方法為CompareTo(),此處為Compare()方法)
// 自定義比較方法類
class PeopleComparer : IComparer<People>
{
// 區(qū)別于CompareTo()單參數(shù),此處為雙參數(shù)
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;
}
}
// 客戶端
// 傳入?yún)?shù)為自定義比較類的實(shí)例
peopleList.Sort(new PeopleComparer());
// OUTPUT:
// 李四 18
// 王五 16
// 王五 30
// 張三 22
// 張三 24
同理,List<T>.Sort(int index, int count, IComparer<T> Comparer) 方法的參數(shù):待排元素起始索引、待排元素個(gè)數(shù)、排序方法
方法三、采用泛型委托 Comparison<T>,綁定自定義的比較方法
區(qū)別于上述繼承接口的方法,此方法的參數(shù)為 泛型委托 Comparison<T>
委托原型:public delegate int Comparison<in T>(T x, T y);
方法三:依照委托的使用方法,首先創(chuàng)建委托實(shí)例MyComparison,并綁定到自定義的比較方法PeopleComparison()上,最終調(diào)用list.Sort()時(shí) 將委托實(shí)例傳入
原理:list.Sort()根據(jù)傳入的委托方法,進(jìn)行兩兩元素比較最終實(shí)現(xiàn)排序
// 客戶端
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)
{
/ 創(chuàng)建list ... /
// 方法0 創(chuàng)建委托實(shí)例并綁定
Comparison<People> MyComparison = PeopleComparison;
// 傳入該實(shí)例實(shí)現(xiàn)比較方法
peopleList.Sort(MyComparison);
// OUTPUT:
// 李四 18
// 王五 16
// 王五 30
// 張三 22
// 張三 24
}
}
此外,既然Comparison<T>是泛型委托,則完全可以用 Lambda表達(dá)式 進(jìn)行描述:
// Lambda表達(dá)式實(shí)現(xiàn)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
總結(jié)
雖然本文僅使用了List<T>一種容器對(duì)Sort()方法進(jìn)行闡述,但是不同容器的使用Sort()的方法大相徑庭,因?yàn)楹诵牡脑矶际菓?yīng)用兩種接口及泛型委托:
兩種接口:IComparable<T> 、 IComparer<T>
泛型委托:Comparison<T>
參考
附:一個(gè)完整的測(cè)試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元素內(nèi)容
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("排序前原始數(shù)據(jù):");
DisplayInfo(peopleList);
System.Console.WriteLine("------------------------------------");
System.Console.WriteLine("方法1排序后數(shù)據(jù):");
peopleList.Sort();
DisplayInfo(peopleList);
System.Console.WriteLine("方法2排序后數(shù)據(jù):");
DisplayInfo(peopleList);
// 方法1 使用IComparer<T>接口。
peopleList.Sort(new PeopleComparer());
// 方法2 除以上兩種方法以外還可以使用另一種方法,在People類中實(shí)現(xiàn)IComparable<T>
peopleList.Sort();
System.Console.WriteLine("方法3排序后數(shù)據(jù):");
DisplayInfo(peopleList);
// 方法3 創(chuàng)建泛型委托實(shí)例并綁定
Comparison<People> MyComparison = PeopleComparison;
// 傳入該實(shí)例實(shí)現(xiàn)比較方法
peopleList.Sort(MyComparison);
System.Console.WriteLine("方法3排序后數(shù)據(jù):");
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;
}
}
}
補(bǔ)充:C# IComparable和IComparer接口和自定義比較器
前言
ArrayList里面有一個(gè)方法:
public virtual void Sort(IComparer comparer);
使用指定的比較器對(duì)整個(gè) System.Collections.ArrayList 中的元素進(jìn)行排序。
comparer:比較元素時(shí)要使用的 System.Collections.IComparer 實(shí)現(xiàn)。
啥玩意???
正文
1.Comparer類簡(jiǎn)單介紹
想弄清楚這個(gè),我們先來看看這么一個(gè)類。
在System.Collections名稱空間中,有這么一個(gè)類:Comparer。顧名思義,他可以實(shí)現(xiàn)對(duì)簡(jiǎn)單類型的比較,什么意思呢?來看如下代碼:
int a=1,b=2;
正常情況下,我們要怎樣比較他們的大???if,運(yùn)算符,……?這當(dāng)然可以,不過Comparer已經(jīng)給我們提供了一個(gè)函數(shù),可以直接使用:(需要using System.Collections;)
Console.WriteLine(Comparer.Default.Compare(a,b));
因?yàn)閍<b,所以控制臺(tái)會(huì)輸出-1。(這個(gè)函數(shù)總是返回-1,0,1三個(gè)值。)
這里通過Comparer里的靜態(tài)屬性Default獲得Comparer的實(shí)例調(diào)用了Comparer里的非靜態(tài)函數(shù)Compare。
(還可以比較根據(jù)字母比較兩個(gè)string類型,這里就省略介紹了)
2.自定義比較器,IComparable,IComparer接口
當(dāng)然,這個(gè)類不僅僅只是用來比較兩個(gè)數(shù)的大小的。有時(shí)候我們想直接比較兩個(gè)對(duì)象,但是引用里面的屬性或許比較麻煩。尤其是參考要素過多,不好直接比較的時(shí)候,怎樣才能更高效地比較兩個(gè)對(duì)象呢?這時(shí)候,我們就需要自定義比較器了。
首先來介紹IComparable接口。這個(gè)接口里只有一個(gè)方法CompareTo()。讓你的類實(shí)現(xiàn)這個(gè)接口的CompareTo方法,就可以直接調(diào)用這個(gè)方法和另一個(gè)對(duì)象比較。下面是例子:
public class ClassTest : IComparable
{
public int intTest;
public int CompareTo(object obj)
{
return intTest-((ClassTest)obj).intTest;
//這里的代碼可以按需要自己編寫,這里只是一個(gè)簡(jiǎn)單的示例
}
}
然后就可以直接使用啦:
ClassTest a = new ClassTest(){intTest=1};
ClassTest b = new ClassTest(){intTest=2};
Console.WriteLine(a.CompareTo(b));//輸出-1
Comparer類已經(jīng)為我們提供了IComparer的默認(rèn)實(shí)現(xiàn),但我們?nèi)匀豢梢宰远x它。新建一個(gè)類:(記得using System.Collections;)
public class ClassTestComparer : IComparer
{
public static IComparer Default = new ClassTestComparer();
//這里必須使用這樣的定義,將對(duì)象轉(zhuǎn)化為IComparer類型有很大用處,下面會(huì)介紹
public int Compare(object a,object b)
{
return ((ClassTest)a).intTest - ((ClassTest)b).intTest;
//同樣這里使用最簡(jiǎn)單的示例,但是你可以大放異彩
}
}
注意,如果用于比較的類和設(shè)定的類不一樣,就會(huì)出現(xiàn)錯(cuò)誤。
使用示例:
ClassTest a = new ClassTest(){intTest=1};
ClassTest b = new ClassTest(){intTest=2};
Console.WriteLine(ClassTestComparer.Default.Compare(a,b));
//結(jié)果是-1
可以發(fā)現(xiàn),這兩個(gè)接口的不同之處在于:IComparable在要比較的對(duì)象的類中實(shí)現(xiàn),可以比較該對(duì)象和另一個(gè)對(duì)象。IComparer在一個(gè)單獨(dú)的類中實(shí)現(xiàn),可以比較任意兩個(gè)對(duì)象(關(guān)鍵是你的設(shè)置)。
3.對(duì)集合排序
當(dāng)然,這兩個(gè)接口還有更強(qiáng)大的用處。我們可以使用這兩個(gè)接口對(duì)集合進(jìn)行排序。還記得前言里的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,將調(diào)用類中的CompareTo()方法,因?yàn)镃lassTest實(shí)現(xiàn)了這個(gè)方法,所以是可以調(diào)用的。如果沒有實(shí)現(xiàn),編譯器會(huì)報(bào)錯(cuò)。
ClassTests.Sort(ClassTestComparer.Default);
//這將使用Compare()方法對(duì)集合中的元素排序。ClassTestComparer類實(shí)現(xiàn)了這個(gè)方法,并且提供了一個(gè)IComparer類型的屬性。
需要注意的是:
兩個(gè)接口提供的方法返回值都是int類型的,負(fù)數(shù)代表小于,0代表等于,正數(shù)代表大于。所以對(duì)數(shù)字之外的自定義比較器,需要人工設(shè)定什么是“大”,什么是“小”。所以上文示例中兩個(gè)數(shù)直接相減,就可以比較大小。
排序完之后,按照返回的int值,集合是由小到大排列的。
使用無參Sort()時(shí),集合中至少要有一個(gè)類實(shí)現(xiàn)了IComparable,否則會(huì)報(bào)錯(cuò)。
一般來說,都是對(duì)同一個(gè)類進(jìn)行比較。不過,也可以實(shí)現(xiàn)對(duì)不同類比較的代碼,這就看具體需要了。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
c# wpf使用GMap.NET類庫,實(shí)現(xiàn)地圖軌跡回放
這篇文章主要介紹了c# wpf使用GMap.NET類庫,實(shí)現(xiàn)地圖軌跡回放的方法,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-03-03
C#/VB.NET實(shí)現(xiàn)在PDF文檔中創(chuàng)建表格
表格是一種直觀高效的數(shù)據(jù)展示方式,可以按行和列的形式呈現(xiàn)數(shù)據(jù),從而更容易吸引讀者的注意,本文將介紹如何使用 Spire.PDF for .NET 通過 .NET 程序在 PDF 文檔中創(chuàng)建表格,需要的可以參考下2023-12-12
C#實(shí)現(xiàn)復(fù)制文件夾中文件到另一個(gè)文件夾的方法
這篇文章主要介紹了C#實(shí)現(xiàn)復(fù)制文件夾中文件到另一個(gè)文件夾的方法,實(shí)例分析了C#實(shí)現(xiàn)文件夾的查找、判斷及文件復(fù)制相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07

