集合内的比较和排序
System.Collections 类在管理集合所涉及的几乎所有进程中执行比较,无论是搜索待删除的元素或返回键值对的值。
集合通常使用相等比较器和/或排序比较器。 有两个构造用于比较。
检查是否相等
诸如 Contains
、 IndexOf、 LastIndexOf和 Remove
的方法将相等比较器用于集合元素。 如果集合是泛型的,则按照以下原则比较项是否相等:
如果类型 T 实现 IEquatable<T> 泛型接口,则相等比较器是该接口的 Equals 方法。
如果类型 T 未实现 IEquatable<T>,则使用 Object.Equals 。
此外,字典集合的某些构造函数重载接受 IEqualityComparer<T> 实现,用于比较键是否相等。 有关示例,请参见 Dictionary<TKey,TValue> 构造函数。
确定排序顺序
BinarySearch
和 Sort
等方法将排序比较器用于集合元素。 可在集合的元素间进行比较,或在元素或指定值之间进行比较。 对于比较对象,有 default comparer
和 explicit comparer
的概念。
默认比较器依赖至少一个正在被比较的对象来实现 IComparable 接口。 在用作列表集合中的值,或用作字典集合中的键的所有类上实现 IComparable 是一种良好做法。 对泛型集合而言,等同性比较是根据以下内容确定的:
如果类型 T 实现 System.IComparable<T> 泛型接口,则默认比较器是该接口的 IComparable<T>.CompareTo(T) 方法。
如果类型 T 实现非泛型 System.IComparable 接口,则默认比较器是该接口的 IComparable.CompareTo(Object) 方法。
如果类型 T 未实现任何接口,则没有默认比较器,必须显式提供一个比较器或比较委托。
为了提供显式比较,某些方法接受 IComparer 实现作为参数。 例如, List<T>.Sort 方法接受 System.Collections.Generic.IComparer<T> 实现。
系统当前的区域性设置可影响集合中的比较和排序。 默认情况下, Collections 类中的比较和排序是区分区域性的。 若要忽略区域性设置并因此获得一致的比较和排序结果,请使用具有接受 InvariantCulture 的成员重载的 CultureInfo。 有关详细信息,请参阅在集合中执行不区分区域性的字符串操作和在数组中执行不区分区域性的字符串操作。
等同性和排序示例
以下代码展示了 IEquatable<T> 和 IComparable<T> 在简单的业务对象上的实现。 此外,如果对象被存储在列表中并已排序,那么你会发现调用 Sort() 方法会导致 Part
类型使用默认比较器,并通过使用匿名方法实现 Sort(Comparison<T>) 方法。
using System;
using System.Collections.Generic;
// Simple business object. A PartId is used to identify the
// type of part but the part name can change.
public class Part : IEquatable<Part>, IComparable<Part>
{
public string PartName { get; set; }
public int PartId { get; set; }
public override string ToString() =>
$"ID: {PartId} Name: {PartName}";
public override bool Equals(object obj) =>
(obj is Part part)
? Equals(part)
: false;
public int SortByNameAscending(string name1, string name2) =>
name1?.CompareTo(name2) ?? 1;
// Default comparer for Part type.
// A null value means that this object is greater.
public int CompareTo(Part comparePart) =>
comparePart == null ? 1 : PartId.CompareTo(comparePart.PartId);
public override int GetHashCode() => PartId;
public bool Equals(Part other) =>
other is null ? false : PartId.Equals(other.PartId);
// Should also override == and != operators.
}
public class Example
{
public static void Main()
{
// Create a list of parts.
var parts = new List<Part>
{
// Add parts to the list.
new Part { PartName = "regular seat", PartId = 1434 },
new Part { PartName = "crank arm", PartId = 1234 },
new Part { PartName = "shift lever", PartId = 1634 },
// Name intentionally left null.
new Part { PartId = 1334 },
new Part { PartName = "banana seat", PartId = 1444 },
new Part { PartName = "cassette", PartId = 1534 }
};
// Write out the parts in the list. This will call the overridden
// ToString method in the Part class.
Console.WriteLine("\nBefore sort:");
parts.ForEach(Console.WriteLine);
// Call Sort on the list. This will use the
// default comparer, which is the Compare method
// implemented on Part.
parts.Sort();
Console.WriteLine("\nAfter sort by part number:");
parts.ForEach(Console.WriteLine);
// This shows calling the Sort(Comparison<T> comparison) overload using
// a lambda expression as the Comparison<T> delegate.
// This method treats null as the lesser of two values.
parts.Sort((Part x, Part y) =>
x.PartName == null && y.PartName == null
? 0
: x.PartName == null
? -1
: y.PartName == null
? 1
: x.PartName.CompareTo(y.PartName));
Console.WriteLine("\nAfter sort by name:");
parts.ForEach(Console.WriteLine);
/*
Before sort:
ID: 1434 Name: regular seat
ID: 1234 Name: crank arm
ID: 1634 Name: shift lever
ID: 1334 Name:
ID: 1444 Name: banana seat
ID: 1534 Name: cassette
After sort by part number:
ID: 1234 Name: crank arm
ID: 1334 Name:
ID: 1434 Name: regular seat
ID: 1444 Name: banana seat
ID: 1534 Name: cassette
ID: 1634 Name: shift lever
After sort by name:
ID: 1334 Name:
ID: 1444 Name: banana seat
ID: 1534 Name: cassette
ID: 1234 Name: crank arm
ID: 1434 Name: regular seat
ID: 1634 Name: shift lever
*/
}
}
Imports System.Collections.Generic
' Simple business object. A PartId is used to identify the type of part
' but the part name can change.
Public Class Part
Implements IEquatable(Of Part)
Implements IComparable(Of Part)
Public Property PartName() As String
Get
Return m_PartName
End Get
Set(value As String)
m_PartName = Value
End Set
End Property
Private m_PartName As String
Public Property PartId() As Integer
Get
Return m_PartId
End Get
Set(value As Integer)
m_PartId = Value
End Set
End Property
Private m_PartId As Integer
Public Overrides Function ToString() As String
Return "ID: " & PartId & " Name: " & PartName
End Function
Public Overrides Function Equals(obj As Object) As Boolean
If obj Is Nothing Then
Return False
End If
Dim objAsPart As Part = TryCast(obj, Part)
If objAsPart Is Nothing Then
Return False
Else
Return Equals(objAsPart)
End If
End Function
Public Function SortByNameAscending(name1 As String, name2 As String) As Integer
Return name1.CompareTo(name2)
End Function
' Default comparer for Part.
Public Function CompareTo(comparePart As Part) As Integer _
Implements IComparable(Of ListSortVB.Part).CompareTo
' A null value means that this object is greater.
If comparePart Is Nothing Then
Return 1
Else
Return Me.PartId.CompareTo(comparePart.PartId)
End If
End Function
Public Overrides Function GetHashCode() As Integer
Return PartId
End Function
Public Overloads Function Equals(other As Part) As Boolean Implements IEquatable(Of ListSortVB.Part).Equals
If other Is Nothing Then
Return False
End If
Return (Me.PartId.Equals(other.PartId))
End Function
' Should also override == and != operators.
End Class
Public Class Example
Public Shared Sub Main()
' Create a list of parts.
Dim parts As New List(Of Part)()
' Add parts to the list.
parts.Add(New Part() With { _
.PartName = "regular seat", _
.PartId = 1434 _
})
parts.Add(New Part() With { _
.PartName = "crank arm", _
.PartId = 1234 _
})
parts.Add(New Part() With { _
.PartName = "shift lever", _
.PartId = 1634 _
})
' Name intentionally left null.
parts.Add(New Part() With { _
.PartId = 1334 _
})
parts.Add(New Part() With { _
.PartName = "banana seat", _
.PartId = 1444 _
})
parts.Add(New Part() With { _
.PartName = "cassette", _
.PartId = 1534 _
})
' Write out the parts in the list. This will call the overridden
' ToString method in the Part class.
Console.WriteLine(vbLf & "Before sort:")
For Each aPart As Part In parts
Console.WriteLine(aPart)
Next
' Call Sort on the list. This will use the
' default comparer, which is the Compare method
' implemented on Part.
parts.Sort()
Console.WriteLine(vbLf & "After sort by part number:")
For Each aPart As Part In parts
Console.WriteLine(aPart)
Next
' This shows calling the Sort(Comparison(T) overload using
' an anonymous delegate method.
' This method treats null as the lesser of two values.
parts.Sort(Function(x As Part, y As Part)
If x.PartName Is Nothing AndAlso y.PartName Is Nothing Then
Return 0
ElseIf x.PartName Is Nothing Then
Return -1
ElseIf y.PartName Is Nothing Then
Return 1
Else
Return x.PartName.CompareTo(y.PartName)
End If
End Function)
Console.WriteLine(vbLf & "After sort by name:")
For Each aPart As Part In parts
Console.WriteLine(aPart)
Next
'
'
' Before sort:
' ID: 1434 Name: regular seat
' ID: 1234 Name: crank arm
' ID: 1634 Name: shift lever
' ID: 1334 Name:
' ID: 1444 Name: banana seat
' ID: 1534 Name: cassette
'
' After sort by part number:
' ID: 1234 Name: crank arm
' ID: 1334 Name:
' ID: 1434 Name: regular seat
' ID: 1444 Name: banana seat
' ID: 1534 Name: cassette
' ID: 1634 Name: shift lever
'
' After sort by name:
' ID: 1334 Name:
' ID: 1444 Name: banana seat
' ID: 1534 Name: cassette
' ID: 1234 Name: crank arm
' ID: 1434 Name: regular seat
' ID: 1634 Name: shift lever
End Sub
End Class