Skip to content
Merged
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
13 changes: 1 addition & 12 deletions src/libraries/System.Data.Common/src/System/Data/DataKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,20 +168,9 @@ internal bool Equals(DataKey value)
{
return false;
}
else if (column1.Length != column2.Length)
{
return false;
}
else
{
for (int i = 0; i < column1.Length; i++)
{
if (!column1[i].Equals(column2[i]))
{
return false;
}
}
return true;
return column1.AsSpan().SequenceEqual(column2);
}
}

Expand Down
17 changes: 1 addition & 16 deletions src/libraries/System.Data.Common/src/System/Data/RelatedView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,7 @@ public bool Invoke(DataRow row, DataRowVersion version)

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

bool allow = true;
if (childValues.Length != parentValues.Length)
{
allow = false;
}
else
{
for (int i = 0; i < childValues.Length; i++)
{
if (!childValues[i].Equals(parentValues[i]))
{
allow = false;
break;
}
}
}
bool allow = childValues.AsSpan().SequenceEqual(parentValues);

IFilter? baseFilter = base.GetFilter();
if (baseFilter != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,7 @@ private ArrayComparer() // use the static Instance singleton
{
}

public bool Equals(T[] x, T[] y)
{
if (x.Length != y.Length)
{
return false;
}
for (int i = 0; i < x.Length; i++)
{
if (!EqualityComparer<T>.Default.Equals(x[i], y[i]))
{
return false;
}
}
return true;
}
public bool Equals(T[] x, T[] y) => x.AsSpan().SequenceEqual(y);

public int GetHashCode(T[] obj)
{
Expand Down
23 changes: 7 additions & 16 deletions src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,22 @@ public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnum
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second);
}

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

comparer = EqualityComparer<TSource>.Default;
}

if (first is ICollection<TSource> firstCol && second is ICollection<TSource> secondCol)
{
if (firstCol.Count != secondCol.Count)
{
return false;
}

if (firstCol is IList<TSource> firstList && secondCol is IList<TSource> secondList)
{
comparer ??= EqualityComparer<TSource>.Default;

int count = firstCol.Count;
for (int i = 0; i < count; i++)
{
Expand All @@ -65,6 +54,8 @@ public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnum
using (IEnumerator<TSource> e1 = first.GetEnumerator())
using (IEnumerator<TSource> e2 = second.GetEnumerator())
{
comparer ??= EqualityComparer<TSource>.Default;

while (e1.MoveNext())
{
if (!(e2.MoveNext() && comparer.Equals(e1.Current, e2.Current)))
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public static void Reverse<T>(this System.Span<T> span) { }
public static int SequenceCompareTo<T>(this System.Span<T> span, System.ReadOnlySpan<T> other) where T : System.IComparable<T> { throw null; }
public static bool SequenceEqual<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other) where T : System.IEquatable<T> { throw null; }
public static bool SequenceEqual<T>(this System.Span<T> span, System.ReadOnlySpan<T> other) where T : System.IEquatable<T> { throw null; }
public static bool SequenceEqual<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other, System.Collections.Generic.IEqualityComparer<T>? comparer = null) { throw null; }
public static bool SequenceEqual<T>(this System.Span<T> span, System.ReadOnlySpan<T> other, System.Collections.Generic.IEqualityComparer<T>? comparer = null) { throw null; }
public static void Sort<T>(this System.Span<T> span) { }
public static void Sort<T>(this System.Span<T> span, System.Comparison<T> comparison) { }
public static void Sort<TKey, TValue>(this System.Span<TKey> keys, System.Span<TValue> items) { }
Expand Down
105 changes: 88 additions & 17 deletions src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.T.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 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 System.Diagnostics.CodeAnalysis;
using Xunit;

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

ReadOnlySpan<int> first = new ReadOnlySpan<int>(a, 1, 0);
ReadOnlySpan<int> second = new ReadOnlySpan<int>(a, 2, 0);
bool b = first.SequenceEqual(second);
Assert.True(b);

Assert.True(first.SequenceEqual(second));
Assert.True(first.SequenceEqual(second, null));
Assert.True(first.SequenceEqual(second, EqualityComparer<int>.Default));
}

[Fact]
public static void SameSpanSequenceEqual()
{
int[] a = { 4, 5, 6 };
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a);
bool b = span.SequenceEqual(span);
Assert.True(b);

Assert.True(span.SequenceEqual(span));
Assert.True(span.SequenceEqual(span, null));
Assert.True(span.SequenceEqual(span, EqualityComparer<int>.Default));
}

[Fact]
Expand All @@ -33,12 +39,17 @@ public static void LengthMismatchSequenceEqual()
int[] a = { 4, 5, 6 };
ReadOnlySpan<int> first = new ReadOnlySpan<int>(a, 0, 3);
ReadOnlySpan<int> second = new ReadOnlySpan<int>(a, 0, 2);
bool b = first.SequenceEqual(second);
Assert.False(b);

Assert.False(first.SequenceEqual(second));
Assert.False(first.SequenceEqual(second, null));
Assert.False(first.SequenceEqual(second, EqualityComparer<int>.Default));
}

[Fact]
public static void OnSequenceEqualOfEqualSpansMakeSureEveryElementIsCompared()
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public static void OnSequenceEqualOfEqualSpansMakeSureEveryElementIsCompared(int mode)
{
for (int length = 0; length < 100; length++)
{
Expand All @@ -53,8 +64,13 @@ public static void OnSequenceEqualOfEqualSpansMakeSureEveryElementIsCompared()

ReadOnlySpan<TInt> firstSpan = new ReadOnlySpan<TInt>(first);
ReadOnlySpan<TInt> secondSpan = new ReadOnlySpan<TInt>(second);
bool b = firstSpan.SequenceEqual(secondSpan);
Assert.True(b);

Assert.True(mode switch
{
0 => firstSpan.SequenceEqual(secondSpan),
1 => firstSpan.SequenceEqual(secondSpan, null),
_ => firstSpan.SequenceEqual(secondSpan, EqualityComparer<TInt>.Default)
});

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

[Fact]
public static void SequenceEqualNoMatch()
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public static void SequenceEqualNoMatch(int mode)
{
for (int length = 1; length < 32; length++)
{
Expand All @@ -88,8 +107,13 @@ public static void SequenceEqualNoMatch()

ReadOnlySpan<TInt> firstSpan = new ReadOnlySpan<TInt>(first);
ReadOnlySpan<TInt> secondSpan = new ReadOnlySpan<TInt>(second);
bool b = firstSpan.SequenceEqual(secondSpan);
Assert.False(b);

Assert.False(mode switch
{
0 => firstSpan.SequenceEqual(secondSpan),
1 => firstSpan.SequenceEqual(secondSpan, null),
_ => firstSpan.SequenceEqual(secondSpan, EqualityComparer<TInt>.Default)
});

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

ReadOnlySpan<TInt> firstSpan = new ReadOnlySpan<TInt>(first, GuardLength, length);
ReadOnlySpan<TInt> secondSpan = new ReadOnlySpan<TInt>(second, GuardLength, length);
bool b = firstSpan.SequenceEqual(secondSpan);
Assert.True(b);

Assert.True(firstSpan.SequenceEqual(secondSpan));
Assert.True(firstSpan.SequenceEqual(secondSpan, null));
Assert.True(firstSpan.SequenceEqual(secondSpan, EqualityComparer<TInt>.Default));
}
}

Expand All @@ -135,8 +161,53 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange()
public static void SequenceEqualsNullData_String(string[] firstInput, string[] secondInput, bool expected)
{
ReadOnlySpan<string> theStrings = firstInput;

Assert.Equal(expected, theStrings.SequenceEqual(secondInput));
Assert.Equal(expected, theStrings.SequenceEqual((ReadOnlySpan<string>)secondInput));
Assert.Equal(expected, theStrings.SequenceEqual(secondInput, null));
Assert.Equal(expected, theStrings.SequenceEqual(secondInput, EqualityComparer<string>.Default));
}

[Fact]
public static void SequenceEqual_AlwaysTrueComparer()
{
Assert.False(((ReadOnlySpan<int>)new int[1]).SequenceEqual(new int[2], new AlwaysComparer<int>(true)));
Assert.True(((ReadOnlySpan<int>)new int[2]).SequenceEqual(new int[2], new AlwaysComparer<int>(true)));
Assert.True(((ReadOnlySpan<int>)new int[2] { 1, 3 }).SequenceEqual(new int[2] { 2, 4 }, new AlwaysComparer<int>(true)));
}

[Fact]
public static void SequenceEqual_AlwaysFalseComparer()
{
Assert.False(((ReadOnlySpan<int>)new int[1]).SequenceEqual(new int[2], new AlwaysComparer<int>(false)));
Assert.False(((ReadOnlySpan<int>)new int[1]).SequenceEqual(new int[2], new AlwaysComparer<int>(false)));
Assert.False(((ReadOnlySpan<int>)new int[2] { 1, 3 }).SequenceEqual(new int[2] { 2, 4 }, new AlwaysComparer<int>(false)));
}

[Fact]
public static void SequenceEqual_IgnoreCaseComparer()
{
string[] lower = new[] { "hello", "world" };
string[] upper = new[] { "HELLO", "WORLD" };
string[] different = new[] { "hello", "wurld" };

Assert.True(((ReadOnlySpan<string>)lower).SequenceEqual(lower));
Assert.False(((ReadOnlySpan<string>)lower).SequenceEqual(upper));
Assert.True(((ReadOnlySpan<string>)upper).SequenceEqual(upper));

Assert.True(((ReadOnlySpan<string>)lower).SequenceEqual(lower, StringComparer.OrdinalIgnoreCase));
Assert.True(((ReadOnlySpan<string>)lower).SequenceEqual(upper, StringComparer.OrdinalIgnoreCase));
Assert.True(((ReadOnlySpan<string>)upper).SequenceEqual(upper, StringComparer.OrdinalIgnoreCase));

Assert.False(((ReadOnlySpan<string>)lower).SequenceEqual(different));
Assert.False(((ReadOnlySpan<string>)lower).SequenceEqual(different, StringComparer.OrdinalIgnoreCase));
}

private sealed class AlwaysComparer<T> : IEqualityComparer<T>
{
private readonly bool _result;
public AlwaysComparer(bool result) => _result = result;
public bool Equals(T? x, T? y) => _result;
public int GetHashCode([DisallowNull] T obj) => 0;
}
}
}
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 @@ -14,26 +15,32 @@ public static void ZeroLengthSequenceEqual_Byte()

ReadOnlySpan<byte> first = new ReadOnlySpan<byte>(a, 1, 0);
ReadOnlySpan<byte> second = new ReadOnlySpan<byte>(a, 2, 0);
bool b = first.SequenceEqual<byte>(second);
Assert.True(b);

Assert.True(first.SequenceEqual<byte>(second));
Assert.True(first.SequenceEqual<byte>(second, null));
Assert.True(first.SequenceEqual<byte>(second, EqualityComparer<byte>.Default));
}

[Fact]
public static void SameSpanSequenceEqual_Byte()
{
byte[] a = { 4, 5, 6 };
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);
bool b = span.SequenceEqual<byte>(span);
Assert.True(b);

Assert.True(span.SequenceEqual<byte>(span));
Assert.True(span.SequenceEqual<byte>(span, null));
Assert.True(span.SequenceEqual<byte>(span, EqualityComparer<byte>.Default));
}

[Fact]
public static void SequenceEqualArrayImplicit_Byte()
{
byte[] a = { 4, 5, 6 };
ReadOnlySpan<byte> first = new ReadOnlySpan<byte>(a, 0, 3);
bool b = first.SequenceEqual<byte>(a);
Assert.True(b);

Assert.True(first.SequenceEqual<byte>(a));
Assert.True(first.SequenceEqual<byte>(a, null));
Assert.True(first.SequenceEqual<byte>(a, EqualityComparer<byte>.Default));
}

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

ReadOnlySpan<byte> first = new ReadOnlySpan<byte>(src, 0, 3);
bool b = first.SequenceEqual<byte>(segment);
Assert.True(b);

Assert.True(first.SequenceEqual<byte>(segment));
Assert.True(first.SequenceEqual<byte>(segment, null));
Assert.True(first.SequenceEqual<byte>(segment, EqualityComparer<byte>.Default));
}

[Fact]
Expand All @@ -54,8 +63,10 @@ public static void LengthMismatchSequenceEqual_Byte()
byte[] a = { 4, 5, 6 };
ReadOnlySpan<byte> first = new ReadOnlySpan<byte>(a, 0, 3);
ReadOnlySpan<byte> second = new ReadOnlySpan<byte>(a, 0, 2);
bool b = first.SequenceEqual<byte>(second);
Assert.False(b);

Assert.False(first.SequenceEqual<byte>(second));
Assert.False(first.SequenceEqual<byte>(second, null));
Assert.False(first.SequenceEqual<byte>(second, EqualityComparer<byte>.Default));
}

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

ReadOnlySpan<byte> firstSpan = new ReadOnlySpan<byte>(first);
ReadOnlySpan<byte> secondSpan = new ReadOnlySpan<byte>(second);
bool b = firstSpan.SequenceEqual<byte>(secondSpan);
Assert.False(b);

Assert.False(firstSpan.SequenceEqual<byte>(secondSpan));
Assert.False(firstSpan.SequenceEqual<byte>(secondSpan, null));
Assert.False(firstSpan.SequenceEqual<byte>(secondSpan, EqualityComparer<byte>.Default));
}
}
}
Expand All @@ -95,8 +108,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange_Byte()
second[length + 1] = 100;
ReadOnlySpan<byte> span1 = new ReadOnlySpan<byte>(first, 1, length);
ReadOnlySpan<byte> span2 = new ReadOnlySpan<byte>(second, 1, length);
bool b = span1.SequenceEqual<byte>(span2);
Assert.True(b);

Assert.True(span1.SequenceEqual<byte>(span2));
Assert.True(span1.SequenceEqual<byte>(span2, null));
Assert.True(span1.SequenceEqual<byte>(span2, EqualityComparer<byte>.Default));
}
}
}
Expand Down
Loading