Skip to content

Commit 7bae40c

Browse files
committed
Implement EqualityComparer<T>.Create with key selector parameters
1 parent 812f3a1 commit 7bae40c

3 files changed

Lines changed: 80 additions & 0 deletions

File tree

src/libraries/System.Collections/ref/System.Collections.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,7 @@ public abstract partial class EqualityComparer<T> : System.Collections.Generic.I
756756
{
757757
protected EqualityComparer() { }
758758
public static System.Collections.Generic.EqualityComparer<T> Create(System.Func<T?, T?, bool> equals, System.Func<T, int>? getHashCode = null) { throw null; }
759+
public static System.Collections.Generic.EqualityComparer<T> Create<TKey>(System.Func<T?, TKey?> keySelector, System.Collections.Generic.IEqualityComparer<TKey>? keyComparer = null) { throw null; }
759760
public static System.Collections.Generic.EqualityComparer<T> Default { get { throw null; } }
760761
public abstract bool Equals(T? x, T? y);
761762
public abstract int GetHashCode([System.Diagnostics.CodeAnalysis.DisallowNullAttribute] T obj);

src/libraries/System.Collections/tests/Generic/Comparers/EqualityComparer.Tests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,56 @@ public void EqualityComparerCreate_DelegatesUsed()
518518
Assert.Equal(2, getHashCodeCalls);
519519
}
520520

521+
[Fact]
522+
public void EqualityComparerCreate_KeySelectorNull_Throws()
523+
{
524+
AssertExtensions.Throws<ArgumentNullException>("keySelector", () => EqualityComparer<string>.Create<int>(keySelector: null));
525+
}
526+
527+
[Fact]
528+
public void EqualityComparerCreate_KeySelectorUsed()
529+
{
530+
var original = "foo";
531+
var otherEqualLen = "bar";
532+
var otherLongerLen = "fooo";
533+
534+
var comparer = EqualityComparer<string>.Create(str => str.Length);
535+
536+
Assert.True(comparer.Equals(original, original));
537+
Assert.True(comparer.Equals(original, otherEqualLen));
538+
Assert.False(comparer.Equals(original, otherLongerLen));
539+
}
540+
541+
[Fact]
542+
public void EqualityComparerCreate_KeySelectorComparerUsed()
543+
{
544+
var evenLen1 = "12";
545+
var evenLen2 = "1234";
546+
var evenLen3 = "123456";
547+
var oddLen1 = "1";
548+
var oddLen2 = "123";
549+
550+
bool isEven(int len) => len % 2 == 0;
551+
552+
var evenOrOddComparer = EqualityComparer<int>.Create(equals: (len1, len2) => isEven(len1) == isEven(len2), getHashCode: len => isEven(len) ? 0 : 1);
553+
var comparer = EqualityComparer<string>.Create(str => str?.Length ?? 0, keyComparer: evenOrOddComparer);
554+
555+
Assert.True(comparer.Equals(evenLen1, evenLen1));
556+
Assert.True(comparer.Equals(evenLen1, evenLen2));
557+
Assert.True(comparer.Equals(evenLen1, evenLen3));
558+
Assert.True(comparer.Equals(oddLen1, oddLen2));
559+
560+
Assert.False(comparer.Equals(evenLen1, oddLen1));
561+
Assert.False(comparer.Equals(evenLen1, oddLen2));
562+
Assert.False(comparer.Equals(oddLen1, evenLen2));
563+
564+
Assert.True(comparer.Equals(null, null));
565+
Assert.True(comparer.Equals(evenLen1, null));
566+
Assert.True(comparer.Equals(null, evenLen1));
567+
Assert.False(comparer.Equals(oddLen1, null));
568+
Assert.False(comparer.Equals(null, oddLen1));
569+
}
570+
521571
[Fact]
522572
public void EqualityComparerCreate_ArgsNotDereferenced()
523573
{

src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,35 @@ public static EqualityComparer<T> Create(Func<T?, T?, bool> equals, Func<T, int>
3434
return new DelegateEqualityComparer<T>(equals, getHashCode);
3535
}
3636

37+
/// <summary>
38+
/// Creates an <see cref="EqualityComparer{T}"/> by using the specified key selector and optional key comparer.
39+
/// </summary>
40+
/// <param name="keySelector">The delegate to use to select a comparison key from each element.</param>
41+
/// <param name="keyComparer">An optional comparer to use when comparing keys. The default comparer of <typeparamref name="TKey"/> is used if none is specified.</param>
42+
/// <returns>The new comparer.</returns>
43+
/// <exception cref="ArgumentNullException">The <paramref name="keySelector"/> delegate was null.</exception>
44+
public static EqualityComparer<T> Create<TKey>(Func<T?, TKey?> keySelector, IEqualityComparer<TKey>? keyComparer = null)
45+
{
46+
ArgumentNullException.ThrowIfNull(keySelector);
47+
48+
keyComparer ??= EqualityComparer<TKey>.Default;
49+
50+
return new DelegateEqualityComparer<T>(
51+
equals: (itemX, itemY) =>
52+
keyComparer.Equals(x: keySelector(itemX), y: keySelector(itemY)),
53+
getHashCode: obj =>
54+
{
55+
if (obj is null)
56+
{
57+
return 0;
58+
}
59+
60+
TKey? key = keySelector(obj);
61+
62+
return key is null ? 0 : keyComparer.GetHashCode(key);
63+
});
64+
}
65+
3766
public abstract bool Equals(T? x, T? y);
3867
public abstract int GetHashCode([DisallowNull] T obj);
3968

0 commit comments

Comments
 (0)