コレクション内での比較と並べ替え

System.Collections クラスは、削除する要素を検索するか、キーと値のペアの値を返すかに関係なく、コレクションの管理に関連するほぼすべての処理において比較を実行します。

通常、コレクションは等値比較子か順序比較子、またはその両方を使用します。 比較には 2 つのコンストラクターが使用されます。

等しいかどうかの確認

ContainsIndexOfLastIndexOfRemove などのメソッドは、コレクション要素に対して等値比較子を使用します。 コレクションがジェネリックの場合、次のガイドラインに従ってアイテムの等価性が比較されます。

  • T 型で IEquatable<T> ジェネリック インターフェイスが実装されている場合、等値比較子はそのインターフェイスの Equals メソッドです。

  • T 型で IEquatable<T>が実装されていない場合、 Object.Equals が使用されます。

また、ディクショナリ コレクションのコンストラクターの一部のオーバーロードでは、IEqualityComparer<T> の実装が受け付けられて、キーが等しいかどうかを比較するために使用されます。 例については、 Dictionary<TKey,TValue> コンストラクターに関する説明を参照してください。

並べ替え順序の決定

BinarySearchSort などのメソッドは、コレクション要素に対して順序比較子を使用します。 コレクションの要素間または要素と指定された値との間で比較を実行できます。 オブジェクトの比較には、 default comparerexplicit comparerの概念が適用されます。

既定の比較子は、比較される 1 つ以上のオブジェクトに依存して 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

関連項目