Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MemoryExtensions overloads with comparer #110197

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
stephentoub marked this conversation as resolved.
Show resolved Hide resolved

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));
stephentoub marked this conversation as resolved.
Show resolved Hide resolved

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
Loading