集合內的比較和排序
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的成員多載。 如需詳細資訊,請參閱 Perform culture-insensitive string operations in collections 與 Perform culture-insensitive string operations in arrays。
相等和排序範例
下列程式碼示範簡單商務物件上的 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