Skip to content

[API Proposal]: Add structural equality and order comparison for common collections #77209

Closed as not planned
@eiriktsarpalis

Description

@eiriktsarpalis

Background and motivation

This proposal attempts to consolidate a number of issues requesting IEqualityComparer<T> and IComparer<T> combinators acting on common collection types, e.g. #33873, #44796, #77183 (comment) and #19644. Defining structural equality/order comparison on collection types is a common requirement, for example when working with incremental source generators.

API Proposal

namespace System.Collections.Generic;

public static class EqualityComparer
{
    // Equality comparison using sequence equality
    public static IEqualityComparer<IEnumerable<T>> CreateEnumerableComparer<T>(IEqualityComparer<T> elementComparer = null);
    public static IEqualityComparer<ReadOnlyMemory<T>> CreateMemoryComparer<T>(IEqualityComparer<T> elementComparer = null);
    public static IEqualityComparer<IReadOnlyList<T>> CreateListComparer<T>(IEqualityComparer<T> elementComparer = null);

    // Equality comparison à la HashSetEqualityComparer
    // cf. https://github.com/dotnet/runtime/blob/5ef4c68b08b5f55df91ce62cca86babfdb321162/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSetEqualityComparer.cs
    public static IEqualityComparer<IReadOnlySet<T>> CreateSetComparer<T>(IEqualityComparer<T> elementComparer = null);
    public static IEqualityComparer<IReadOnlyDictionary<TKey, TValue>> CreateDictionaryComparer<TKey, TValue>(IEqualityComparer<TKey> keyComparer= null, IEqualityComparer<TValue> valueComparer = null);
}

public static class Comparer
{
    // Comparison using lexicographic ordering
    public static IComparer<IEnumerable<T>> CreateEnumerableComparer<T>(IComparer<T> elementComparer = null);
    public static IComparer<ReadOnlyMemory<T>> CreateMemoryComparer<T>(IComparer<T> elementComparer = null);
    public static IComparer<IReadOnlyList<T>> CreateListComparer<T>(IComparer<T> elementComparer = null);
}

API Usage

IEqualityComparer<string[]> stringArrayComparer = EqualityComparer.CreateEnumerableComparer(StringComparer.InvariantCultureIgnoreCase);
var dictionary = new Dictionary<string[], object>(stringArrayComparer);

dictionary.Add(new string[] { "key" }, null);
dictionary.ContainsKey(new string[] { "KEY" }); // true
IComparer<string[]> stringArrayComparer = Comparer.CreateEnumerableComparer(StringComparer.InvariantCultureIgnoreCase);
var dictionary = new SortedDictionary<string[], object>(stringArrayComparer);

dictionary.Add(new string[] { "key" }, null);
dictionary.ContainsKey(new string[] { "KEY" }); // true

Alternative Designs

Making the combinators generic on the collection type would allow construction-time specialization and help with devirtualization, e.g.

public static IEqualityComparer<TEnumerable> CreateEnumerableComparer<TEnumerable, TElement>(IEqualityComparer<TElement> elementComparer = null)
     where TEnumerable : IEnumerable<TElement>
{
     elementComparer ??= EqualityComparer<T>.Default;

     if (typeof(IReadOnlyList<TElement>).IsAssignableFrom(typeof(TEnumerable)))
     {
          return new ListSequenceComparer<TEnumerable, TElement>(elementComparer));
     }

     return new EnumerableSequenceComparer<TEnumerable, TElement>(elementComparer);
}

But obviously that would make calling the combinators more complicated:

IEqualityComparer<int[]> arrayComparer = EqualityComparer.CreateEnumerableComparer<int[], int>();

Full alternative proposal:

namespace System.Collections.Generic;

public static class EqualityComparer
{
    // Equality comparison using sequence equality
    public static IEqualityComparer<TEnumerable> CreateEnumerableComparer<TEnumerable, T>(IEqualityComparer<T> elementComparer = null) 
        where TEnumerable : IEnumerable<T> { }

    public static IEqualityComparer<ReadOnlyMemory<T>> CreateMemoryComparer<T>(IEqualityComparer<T> elementComparer = null) { }

    // Equality comparison à la HashSetEqualityComparer
    // cf. https://github.com/dotnet/runtime/blob/5ef4c68b08b5f55df91ce62cca86babfdb321162/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSetEqualityComparer.cs
    public static IEqualityComparer<TSet> CreateSetComparer<TSet, T>(IEqualityComparer<T> elementComparer = null) 
        where TSet : IReadOnly<T> { }

    public static IEqualityComparer<TDictionary> CreateDictionaryComparer<TDictionary, TKey, TValue>(IEqualityComparer<TKey> keyComparer= null, IEqualityComparer<TValue> valueComparer = null) 
        where TDictionary : IDictionary<TKey, TValue> { }
}

public static class Comparer
{
    // Comparison using lexicographic ordering
    public static IComparer<TEnumerable> CreateEnumerableComparer<TEnumerable, T>(IComparer<T> elementComparer = null) 
        where TEnumerable : IEnumerable<T> { }

    public static IComparer<ReadOnlyMemory<T>> CreateMemoryComparer<T>(IComparer<T> elementComparer = null) { }
}

Risks

No response

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions