Skip to content

Commit

Permalink
Add MemoryExtensions overloads with comparer
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub committed Nov 26, 2024
1 parent 1e6311a commit 6958f74
Show file tree
Hide file tree
Showing 17 changed files with 2,658 additions and 867 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,7 @@ internal static void VerifyAttributes(StringDictionary? attributes, string[]? su

foreach (string key in attributes.Keys)
{
bool found = false;
if (supportedAttributes != null)
{
for (int i = 0; i < supportedAttributes.Length; i++)
{
if (supportedAttributes[i] == key)
found = true;
}
}

if (!found)
if (supportedAttributes is null || !supportedAttributes.Contains(key))
{
throw new ArgumentException(SR.Format(SR.AttributeNotSupported, key, parent.GetType().FullName));
}
Expand Down
42 changes: 18 additions & 24 deletions src/libraries/System.Linq/src/System/Linq/Contains.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,43 +18,37 @@ public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource v
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}

if (source.TryGetSpan(out ReadOnlySpan<TSource> span))
{
return span.Contains(value, comparer);
}

if (comparer is null)
{
// While it's tempting, this must not delegate to ICollection<TSource>.Contains, as the historical semantics
// of a null comparer with this method are to use EqualityComparer<TSource>.Default, and that might differ
// from the semantics encoded in ICollection<TSource>.Contains.

// We don't bother special-casing spans here as explicitly providing a null comparer with a known collection type
// is relatively rare. If you don't care about the comparer, you use the other overload, and while it will delegate
// to this overload with a null comparer, it'll only do so for collections from which we can't extract a span.
// And if you do care about the comparer, you're generally passing in a non-null one.

foreach (TSource element in source)
{
if (EqualityComparer<TSource>.Default.Equals(element, value))
{
return true;
}
}
}
else if (source.TryGetSpan(out ReadOnlySpan<TSource> span))
{
foreach (TSource element in span)
if (typeof(TSource).IsValueType)
{
if (comparer.Equals(element, value))
foreach (TSource element in source)
{
return true;
if (EqualityComparer<TSource>.Default.Equals(element, value))
{
return true;
}
}

return false;
}

comparer = EqualityComparer<TSource>.Default;
}
else
foreach (TSource element in source)
{
foreach (TSource element in source)
if (comparer.Equals(element, value))
{
if (comparer.Equals(element, value))
{
return true;
}
return true;
}
}

Expand Down
35 changes: 35 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions src/libraries/System.Memory/tests/ReadOnlySpan/Comparers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using Xunit;

namespace System.SpanTests
{
public static partial class ReadOnlySpanTests
{
private static IEnumerable<IEqualityComparer<T>?> GetDefaultEqualityComparers<T>()
{
yield return null;

yield return EqualityComparer<T>.Default;

yield return EqualityComparer<T>.Create((i, j) => EqualityComparer<T>.Default.Equals(i, j));

if (typeof(T) == typeof(string))
{
yield return (IEqualityComparer<T>)(object)StringComparer.Ordinal;
}
}

private static IEnumerable<IComparer<T>?> GetDefaultComparers<T>()
{
yield return null;

yield return Comparer<T>.Default;

yield return Comparer<T>.Create((i, j) => Comparer<T>.Default.Compare(i, j));

if (typeof(T) == typeof(string))
{
yield return (IComparer<T>)(object)StringComparer.Ordinal;
}
}

private static IEqualityComparer<T> GetFalseEqualityComparer<T>() =>
EqualityComparer<T>.Create((i, j) => false);
}
}
38 changes: 20 additions & 18 deletions src/libraries/System.Memory/tests/ReadOnlySpan/Contains.T.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using Xunit;

namespace System.SpanTests
Expand All @@ -27,13 +28,13 @@ public static void TestContains()
{
a[i] = 10 * (i + 1);
}
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a);

for (int targetIndex = 0; targetIndex < length; targetIndex++)
{
int target = a[targetIndex];
bool found = span.Contains(target);
Assert.True(found);
Assert.True(new ReadOnlySpan<int>(a).Contains(target));
Assert.All(GetDefaultEqualityComparers<int>(), comparer => Assert.True(new ReadOnlySpan<int>(a).Contains(target, comparer)));
Assert.False(new ReadOnlySpan<int>(a).Contains(target, GetFalseEqualityComparer<int>()));
}
}
}
Expand All @@ -52,9 +53,9 @@ public static void TestMultipleContains()
a[length - 1] = 5555;
a[length - 2] = 5555;

ReadOnlySpan<int> span = new ReadOnlySpan<int>(a);
bool found = span.Contains(5555);
Assert.True(found);
Assert.True(new ReadOnlySpan<int>(a).Contains(5555));
Assert.All(GetDefaultEqualityComparers<int>(), comparer => Assert.True(new ReadOnlySpan<int>(a).Contains(5555, comparer)));
Assert.False(new ReadOnlySpan<int>(a).Contains(5555, GetFalseEqualityComparer<int>()));
}
}

Expand All @@ -71,8 +72,7 @@ public static void OnNoMatchForContainsMakeSureEveryElementIsCompared()
a[i] = new TInt(10 * (i + 1), log);
}
ReadOnlySpan<TInt> span = new ReadOnlySpan<TInt>(a);
bool found = span.Contains(new TInt(9999, log));
Assert.False(found);
Assert.False(span.Contains(new TInt(9999, log)));

// Since we asked for a non-existent value, make sure each element of the array was compared once.
// (Strictly speaking, it would not be illegal for IndexOf to compare an element more than once but
Expand Down Expand Up @@ -112,17 +112,19 @@ void checkForOutOfRangeAccess(int x, int y)
}

ReadOnlySpan<TInt> span = new ReadOnlySpan<TInt>(a, GuardLength, length);
bool found = span.Contains(new TInt(9999, checkForOutOfRangeAccess));
Assert.False(found);
Assert.False(span.Contains(new TInt(9999, checkForOutOfRangeAccess)));
}
}

[Fact]
public static void ZeroLengthContains_String()
{
ReadOnlySpan<string> span = new ReadOnlySpan<string>(Array.Empty<string>());
bool found = span.Contains("a");
Assert.False(found);
Assert.False(span.Contains("a"));
Assert.All(GetDefaultEqualityComparers<string>(), comparer => Assert.False(new ReadOnlySpan<string>(Array.Empty<string>()).Contains("a", comparer)));
Assert.False(span.Contains("a", null));
Assert.False(span.Contains("a", EqualityComparer<string>.Default));
Assert.False(span.Contains("a", EqualityComparer<string>.Create((i, j) => i == j)));
}

[Fact]
Expand All @@ -140,8 +142,9 @@ public static void TestMatchContains_String()
for (int targetIndex = 0; targetIndex < length; targetIndex++)
{
string target = a[targetIndex];
bool found = span.Contains(target);
Assert.True(found);
Assert.True(span.Contains(target));
Assert.All(GetDefaultEqualityComparers<string>(), comparer => Assert.True(new ReadOnlySpan<string>(a).Contains(target, comparer)));
Assert.False(span.Contains(target, GetFalseEqualityComparer<string>()));
}
}
}
Expand All @@ -161,8 +164,7 @@ public static void TestNoMatchContains_String()
}
ReadOnlySpan<string> span = new ReadOnlySpan<string>(a);

bool found = span.Contains(target);
Assert.False(found);
Assert.False(span.Contains(target));
}
}

Expand All @@ -181,8 +183,7 @@ public static void TestMultipleMatchContains_String()
a[length - 2] = "5555";

ReadOnlySpan<string> span = new ReadOnlySpan<string>(a);
bool found = span.Contains("5555");
Assert.True(found);
Assert.True(span.Contains("5555"));
}
}

Expand All @@ -192,6 +193,7 @@ public static void ContainsNull_String(string[] spanInput, bool expected)
{
ReadOnlySpan<string> theStrings = spanInput;
Assert.Equal(expected, theStrings.Contains(null));
Assert.All(GetDefaultEqualityComparers<string>(), comparer => Assert.Equal(expected, new ReadOnlySpan<string>(spanInput).Contains(null, comparer)));
}
}
}
Loading

0 comments on commit 6958f74

Please sign in to comment.