Skip to content

Commit 831c061

Browse files
authored
Add new MemoryExtensions.SequenceEqual overloads (#48677)
* Add new MemoryExtensions.SequenceEqual overloads * Use MemoryExtensions.SequenceEqual in Enumerable.SequenceEqual * Use MemoryExtensions.SequenceEqual in a few more places
1 parent afa7e27 commit 831c061

File tree

14 files changed

+357
-188
lines changed

14 files changed

+357
-188
lines changed

src/libraries/System.Data.Common/src/System/Data/DataKey.cs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,9 @@ internal bool Equals(DataKey value)
168168
{
169169
return false;
170170
}
171-
else if (column1.Length != column2.Length)
172-
{
173-
return false;
174-
}
175171
else
176172
{
177-
for (int i = 0; i < column1.Length; i++)
178-
{
179-
if (!column1[i].Equals(column2[i]))
180-
{
181-
return false;
182-
}
183-
}
184-
return true;
173+
return column1.AsSpan().SequenceEqual(column2);
185174
}
186175
}
187176

src/libraries/System.Data.Common/src/System/Data/RelatedView.cs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,7 @@ public bool Invoke(DataRow row, DataRowVersion version)
6262

6363
object[] childValues = row.GetKeyValues(_childKey, version);
6464

65-
bool allow = true;
66-
if (childValues.Length != parentValues.Length)
67-
{
68-
allow = false;
69-
}
70-
else
71-
{
72-
for (int i = 0; i < childValues.Length; i++)
73-
{
74-
if (!childValues[i].Equals(parentValues[i]))
75-
{
76-
allow = false;
77-
break;
78-
}
79-
}
80-
}
65+
bool allow = childValues.AsSpan().SequenceEqual(parentValues);
8166

8267
IFilter? baseFilter = base.GetFilter();
8368
if (baseFilter != null)

src/libraries/System.IO.UnmanagedMemoryStream/tests/ArrayHelpers.cs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,7 @@ private ArrayComparer() // use the static Instance singleton
5353
{
5454
}
5555

56-
public bool Equals(T[] x, T[] y)
57-
{
58-
if (x.Length != y.Length)
59-
{
60-
return false;
61-
}
62-
for (int i = 0; i < x.Length; i++)
63-
{
64-
if (!EqualityComparer<T>.Default.Equals(x[i], y[i]))
65-
{
66-
return false;
67-
}
68-
}
69-
return true;
70-
}
56+
public bool Equals(T[] x, T[] y) => x.AsSpan().SequenceEqual(y);
7157

7258
public int GetHashCode(T[] obj)
7359
{

src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,22 @@ public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnum
2222
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second);
2323
}
2424

25-
if (comparer == null)
25+
if (first is ICollection<TSource> firstCol && second is ICollection<TSource> secondCol)
2626
{
27-
// It's relatively common to see code (especially in tests and testing frameworks) that ends up
28-
// using Enumerable.SequenceEqual to compare two byte arrays. Using ReadOnlySpan.SequenceEqual
29-
// is significantly faster than accessing each byte via the array's IList<byte> interface
30-
// implementation. So, we special-case byte[] here. It would be nice to be able to delegate
31-
// to ReadOnlySpan.SequenceEqual for all TSource[] arrays where TSource is a value type and
32-
// implements IEquatable<TSource>, but there's no good way without reflection to convince
33-
// the C# compiler to let us delegate, as ReadOnlySpan.SequenceEqual requires an IEquatable<T>
34-
// constraint on its type parameter, and Enumerable.SequenceEqual lacks one on its type parameter.
35-
if (typeof(TSource) == typeof(byte) && first is byte[] firstArr && second is byte[] secondArr)
27+
if (first is TSource[] firstArray && second is TSource[] secondArray)
3628
{
37-
return ((ReadOnlySpan<byte>)firstArr).SequenceEqual(secondArr);
29+
return ((ReadOnlySpan<TSource>)firstArray).SequenceEqual(secondArray, comparer);
3830
}
3931

40-
comparer = EqualityComparer<TSource>.Default;
41-
}
42-
43-
if (first is ICollection<TSource> firstCol && second is ICollection<TSource> secondCol)
44-
{
4532
if (firstCol.Count != secondCol.Count)
4633
{
4734
return false;
4835
}
4936

5037
if (firstCol is IList<TSource> firstList && secondCol is IList<TSource> secondList)
5138
{
39+
comparer ??= EqualityComparer<TSource>.Default;
40+
5241
int count = firstCol.Count;
5342
for (int i = 0; i < count; i++)
5443
{
@@ -65,6 +54,8 @@ public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnum
6554
using (IEnumerator<TSource> e1 = first.GetEnumerator())
6655
using (IEnumerator<TSource> e2 = second.GetEnumerator())
6756
{
57+
comparer ??= EqualityComparer<TSource>.Default;
58+
6859
while (e1.MoveNext())
6960
{
7061
if (!(e2.MoveNext() && comparer.Equals(e1.Current, e2.Current)))

src/libraries/System.Memory/ref/System.Memory.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public static void Reverse<T>(this System.Span<T> span) { }
8484
public static int SequenceCompareTo<T>(this System.Span<T> span, System.ReadOnlySpan<T> other) where T : System.IComparable<T> { throw null; }
8585
public static bool SequenceEqual<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other) where T : System.IEquatable<T> { throw null; }
8686
public static bool SequenceEqual<T>(this System.Span<T> span, System.ReadOnlySpan<T> other) where T : System.IEquatable<T> { throw null; }
87+
public static bool SequenceEqual<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other, System.Collections.Generic.IEqualityComparer<T>? comparer = null) { throw null; }
88+
public static bool SequenceEqual<T>(this System.Span<T> span, System.ReadOnlySpan<T> other, System.Collections.Generic.IEqualityComparer<T>? comparer = null) { throw null; }
8789
public static void Sort<T>(this System.Span<T> span) { }
8890
public static void Sort<T>(this System.Span<T> span, System.Comparison<T> comparison) { }
8991
public static void Sort<TKey, TValue>(this System.Span<TKey> keys, System.Span<TValue> items) { }

src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.T.cs

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
46
using Xunit;
57

68
namespace System.SpanTests
@@ -14,17 +16,21 @@ public static void ZeroLengthSequenceEqual()
1416

1517
ReadOnlySpan<int> first = new ReadOnlySpan<int>(a, 1, 0);
1618
ReadOnlySpan<int> second = new ReadOnlySpan<int>(a, 2, 0);
17-
bool b = first.SequenceEqual(second);
18-
Assert.True(b);
19+
20+
Assert.True(first.SequenceEqual(second));
21+
Assert.True(first.SequenceEqual(second, null));
22+
Assert.True(first.SequenceEqual(second, EqualityComparer<int>.Default));
1923
}
2024

2125
[Fact]
2226
public static void SameSpanSequenceEqual()
2327
{
2428
int[] a = { 4, 5, 6 };
2529
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a);
26-
bool b = span.SequenceEqual(span);
27-
Assert.True(b);
30+
31+
Assert.True(span.SequenceEqual(span));
32+
Assert.True(span.SequenceEqual(span, null));
33+
Assert.True(span.SequenceEqual(span, EqualityComparer<int>.Default));
2834
}
2935

3036
[Fact]
@@ -33,12 +39,17 @@ public static void LengthMismatchSequenceEqual()
3339
int[] a = { 4, 5, 6 };
3440
ReadOnlySpan<int> first = new ReadOnlySpan<int>(a, 0, 3);
3541
ReadOnlySpan<int> second = new ReadOnlySpan<int>(a, 0, 2);
36-
bool b = first.SequenceEqual(second);
37-
Assert.False(b);
42+
43+
Assert.False(first.SequenceEqual(second));
44+
Assert.False(first.SequenceEqual(second, null));
45+
Assert.False(first.SequenceEqual(second, EqualityComparer<int>.Default));
3846
}
3947

40-
[Fact]
41-
public static void OnSequenceEqualOfEqualSpansMakeSureEveryElementIsCompared()
48+
[Theory]
49+
[InlineData(0)]
50+
[InlineData(1)]
51+
[InlineData(2)]
52+
public static void OnSequenceEqualOfEqualSpansMakeSureEveryElementIsCompared(int mode)
4253
{
4354
for (int length = 0; length < 100; length++)
4455
{
@@ -53,8 +64,13 @@ public static void OnSequenceEqualOfEqualSpansMakeSureEveryElementIsCompared()
5364

5465
ReadOnlySpan<TInt> firstSpan = new ReadOnlySpan<TInt>(first);
5566
ReadOnlySpan<TInt> secondSpan = new ReadOnlySpan<TInt>(second);
56-
bool b = firstSpan.SequenceEqual(secondSpan);
57-
Assert.True(b);
67+
68+
Assert.True(mode switch
69+
{
70+
0 => firstSpan.SequenceEqual(secondSpan),
71+
1 => firstSpan.SequenceEqual(secondSpan, null),
72+
_ => firstSpan.SequenceEqual(secondSpan, EqualityComparer<TInt>.Default)
73+
});
5874

5975
// Make sure each element of the array was compared once. (Strictly speaking, it would not be illegal for
6076
// SequenceEqual to compare an element more than once but that would be a non-optimal implementation and
@@ -68,8 +84,11 @@ public static void OnSequenceEqualOfEqualSpansMakeSureEveryElementIsCompared()
6884
}
6985
}
7086

71-
[Fact]
72-
public static void SequenceEqualNoMatch()
87+
[Theory]
88+
[InlineData(0)]
89+
[InlineData(1)]
90+
[InlineData(2)]
91+
public static void SequenceEqualNoMatch(int mode)
7392
{
7493
for (int length = 1; length < 32; length++)
7594
{
@@ -88,8 +107,13 @@ public static void SequenceEqualNoMatch()
88107

89108
ReadOnlySpan<TInt> firstSpan = new ReadOnlySpan<TInt>(first);
90109
ReadOnlySpan<TInt> secondSpan = new ReadOnlySpan<TInt>(second);
91-
bool b = firstSpan.SequenceEqual(secondSpan);
92-
Assert.False(b);
110+
111+
Assert.False(mode switch
112+
{
113+
0 => firstSpan.SequenceEqual(secondSpan),
114+
1 => firstSpan.SequenceEqual(secondSpan, null),
115+
_ => firstSpan.SequenceEqual(secondSpan, EqualityComparer<TInt>.Default)
116+
});
93117

94118
Assert.Equal(1, log.CountCompares(first[mismatchIndex].Value, second[mismatchIndex].Value));
95119
}
@@ -125,8 +149,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange()
125149

126150
ReadOnlySpan<TInt> firstSpan = new ReadOnlySpan<TInt>(first, GuardLength, length);
127151
ReadOnlySpan<TInt> secondSpan = new ReadOnlySpan<TInt>(second, GuardLength, length);
128-
bool b = firstSpan.SequenceEqual(secondSpan);
129-
Assert.True(b);
152+
153+
Assert.True(firstSpan.SequenceEqual(secondSpan));
154+
Assert.True(firstSpan.SequenceEqual(secondSpan, null));
155+
Assert.True(firstSpan.SequenceEqual(secondSpan, EqualityComparer<TInt>.Default));
130156
}
131157
}
132158

@@ -135,8 +161,53 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange()
135161
public static void SequenceEqualsNullData_String(string[] firstInput, string[] secondInput, bool expected)
136162
{
137163
ReadOnlySpan<string> theStrings = firstInput;
164+
138165
Assert.Equal(expected, theStrings.SequenceEqual(secondInput));
139-
Assert.Equal(expected, theStrings.SequenceEqual((ReadOnlySpan<string>)secondInput));
166+
Assert.Equal(expected, theStrings.SequenceEqual(secondInput, null));
167+
Assert.Equal(expected, theStrings.SequenceEqual(secondInput, EqualityComparer<string>.Default));
168+
}
169+
170+
[Fact]
171+
public static void SequenceEqual_AlwaysTrueComparer()
172+
{
173+
Assert.False(((ReadOnlySpan<int>)new int[1]).SequenceEqual(new int[2], new AlwaysComparer<int>(true)));
174+
Assert.True(((ReadOnlySpan<int>)new int[2]).SequenceEqual(new int[2], new AlwaysComparer<int>(true)));
175+
Assert.True(((ReadOnlySpan<int>)new int[2] { 1, 3 }).SequenceEqual(new int[2] { 2, 4 }, new AlwaysComparer<int>(true)));
176+
}
177+
178+
[Fact]
179+
public static void SequenceEqual_AlwaysFalseComparer()
180+
{
181+
Assert.False(((ReadOnlySpan<int>)new int[1]).SequenceEqual(new int[2], new AlwaysComparer<int>(false)));
182+
Assert.False(((ReadOnlySpan<int>)new int[1]).SequenceEqual(new int[2], new AlwaysComparer<int>(false)));
183+
Assert.False(((ReadOnlySpan<int>)new int[2] { 1, 3 }).SequenceEqual(new int[2] { 2, 4 }, new AlwaysComparer<int>(false)));
184+
}
185+
186+
[Fact]
187+
public static void SequenceEqual_IgnoreCaseComparer()
188+
{
189+
string[] lower = new[] { "hello", "world" };
190+
string[] upper = new[] { "HELLO", "WORLD" };
191+
string[] different = new[] { "hello", "wurld" };
192+
193+
Assert.True(((ReadOnlySpan<string>)lower).SequenceEqual(lower));
194+
Assert.False(((ReadOnlySpan<string>)lower).SequenceEqual(upper));
195+
Assert.True(((ReadOnlySpan<string>)upper).SequenceEqual(upper));
196+
197+
Assert.True(((ReadOnlySpan<string>)lower).SequenceEqual(lower, StringComparer.OrdinalIgnoreCase));
198+
Assert.True(((ReadOnlySpan<string>)lower).SequenceEqual(upper, StringComparer.OrdinalIgnoreCase));
199+
Assert.True(((ReadOnlySpan<string>)upper).SequenceEqual(upper, StringComparer.OrdinalIgnoreCase));
200+
201+
Assert.False(((ReadOnlySpan<string>)lower).SequenceEqual(different));
202+
Assert.False(((ReadOnlySpan<string>)lower).SequenceEqual(different, StringComparer.OrdinalIgnoreCase));
203+
}
204+
205+
private sealed class AlwaysComparer<T> : IEqualityComparer<T>
206+
{
207+
private readonly bool _result;
208+
public AlwaysComparer(bool result) => _result = result;
209+
public bool Equals(T? x, T? y) => _result;
210+
public int GetHashCode([DisallowNull] T obj) => 0;
140211
}
141212
}
142213
}

src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.byte.cs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Generic;
45
using Xunit;
56

67
namespace System.SpanTests
@@ -14,26 +15,32 @@ public static void ZeroLengthSequenceEqual_Byte()
1415

1516
ReadOnlySpan<byte> first = new ReadOnlySpan<byte>(a, 1, 0);
1617
ReadOnlySpan<byte> second = new ReadOnlySpan<byte>(a, 2, 0);
17-
bool b = first.SequenceEqual<byte>(second);
18-
Assert.True(b);
18+
19+
Assert.True(first.SequenceEqual<byte>(second));
20+
Assert.True(first.SequenceEqual<byte>(second, null));
21+
Assert.True(first.SequenceEqual<byte>(second, EqualityComparer<byte>.Default));
1922
}
2023

2124
[Fact]
2225
public static void SameSpanSequenceEqual_Byte()
2326
{
2427
byte[] a = { 4, 5, 6 };
2528
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);
26-
bool b = span.SequenceEqual<byte>(span);
27-
Assert.True(b);
29+
30+
Assert.True(span.SequenceEqual<byte>(span));
31+
Assert.True(span.SequenceEqual<byte>(span, null));
32+
Assert.True(span.SequenceEqual<byte>(span, EqualityComparer<byte>.Default));
2833
}
2934

3035
[Fact]
3136
public static void SequenceEqualArrayImplicit_Byte()
3237
{
3338
byte[] a = { 4, 5, 6 };
3439
ReadOnlySpan<byte> first = new ReadOnlySpan<byte>(a, 0, 3);
35-
bool b = first.SequenceEqual<byte>(a);
36-
Assert.True(b);
40+
41+
Assert.True(first.SequenceEqual<byte>(a));
42+
Assert.True(first.SequenceEqual<byte>(a, null));
43+
Assert.True(first.SequenceEqual<byte>(a, EqualityComparer<byte>.Default));
3744
}
3845

3946
[Fact]
@@ -44,8 +51,10 @@ public static void SequenceEqualArraySegmentImplicit_Byte()
4451
var segment = new ArraySegment<byte>(dst, 1, 3);
4552

4653
ReadOnlySpan<byte> first = new ReadOnlySpan<byte>(src, 0, 3);
47-
bool b = first.SequenceEqual<byte>(segment);
48-
Assert.True(b);
54+
55+
Assert.True(first.SequenceEqual<byte>(segment));
56+
Assert.True(first.SequenceEqual<byte>(segment, null));
57+
Assert.True(first.SequenceEqual<byte>(segment, EqualityComparer<byte>.Default));
4958
}
5059

5160
[Fact]
@@ -54,8 +63,10 @@ public static void LengthMismatchSequenceEqual_Byte()
5463
byte[] a = { 4, 5, 6 };
5564
ReadOnlySpan<byte> first = new ReadOnlySpan<byte>(a, 0, 3);
5665
ReadOnlySpan<byte> second = new ReadOnlySpan<byte>(a, 0, 2);
57-
bool b = first.SequenceEqual<byte>(second);
58-
Assert.False(b);
66+
67+
Assert.False(first.SequenceEqual<byte>(second));
68+
Assert.False(first.SequenceEqual<byte>(second, null));
69+
Assert.False(first.SequenceEqual<byte>(second, EqualityComparer<byte>.Default));
5970
}
6071

6172
[Fact]
@@ -76,8 +87,10 @@ public static void SequenceEqualNoMatch_Byte()
7687

7788
ReadOnlySpan<byte> firstSpan = new ReadOnlySpan<byte>(first);
7889
ReadOnlySpan<byte> secondSpan = new ReadOnlySpan<byte>(second);
79-
bool b = firstSpan.SequenceEqual<byte>(secondSpan);
80-
Assert.False(b);
90+
91+
Assert.False(firstSpan.SequenceEqual<byte>(secondSpan));
92+
Assert.False(firstSpan.SequenceEqual<byte>(secondSpan, null));
93+
Assert.False(firstSpan.SequenceEqual<byte>(secondSpan, EqualityComparer<byte>.Default));
8194
}
8295
}
8396
}
@@ -95,8 +108,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange_Byte()
95108
second[length + 1] = 100;
96109
ReadOnlySpan<byte> span1 = new ReadOnlySpan<byte>(first, 1, length);
97110
ReadOnlySpan<byte> span2 = new ReadOnlySpan<byte>(second, 1, length);
98-
bool b = span1.SequenceEqual<byte>(span2);
99-
Assert.True(b);
111+
112+
Assert.True(span1.SequenceEqual<byte>(span2));
113+
Assert.True(span1.SequenceEqual<byte>(span2, null));
114+
Assert.True(span1.SequenceEqual<byte>(span2, EqualityComparer<byte>.Default));
100115
}
101116
}
102117
}

0 commit comments

Comments
 (0)