Skip to content

Commit

Permalink
Special-case Enumerable.SequenceEqual for byte[] (#48287)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub authored Feb 23, 2021
1 parent 970d347 commit 24f1acd
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 5 deletions.
23 changes: 18 additions & 5 deletions src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnum

public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource>? comparer)
{
if (comparer == null)
{
comparer = EqualityComparer<TSource>.Default;
}

if (first == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.first);
Expand All @@ -27,6 +22,24 @@ public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnum
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second);
}

if (comparer == null)
{
// 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)
{
return ((ReadOnlySpan<byte>)firstArr).SequenceEqual(secondArr);
}

comparer = EqualityComparer<TSource>.Default;
}

if (first is ICollection<TSource> firstCol && second is ICollection<TSource> secondCol)
{
if (firstCol.Count != secondCol.Count)
Expand Down
32 changes: 32 additions & 0 deletions src/libraries/System.Linq/tests/SequenceEqualTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,37 @@ public void SecondSourceNull()

AssertExtensions.Throws<ArgumentNullException>("second", () => first.SequenceEqual(second));
}

[Fact]
public void ByteArrays_SpecialCasedButExpectedBehavior()
{
AssertExtensions.Throws<ArgumentNullException>("first", () => ((byte[])null).SequenceEqual(new byte[1]));
AssertExtensions.Throws<ArgumentNullException>("second", () => new byte[1].SequenceEqual(null));

Assert.False(new byte[1].SequenceEqual(new byte[0]));
Assert.False(new byte[0].SequenceEqual(new byte[1]));

var r = new Random();
for (int i = 0; i < 32; i++)
{
byte[] arr = new byte[i];
r.NextBytes(arr);

byte[] same = (byte[])arr.Clone();
Assert.True(arr.SequenceEqual(same));
Assert.True(same.SequenceEqual(arr));
Assert.True(same.SequenceEqual(arr.ToList()));
Assert.True(same.ToList().SequenceEqual(arr));
Assert.True(same.ToList().SequenceEqual(arr.ToList()));

if (i > 0)
{
byte[] diff = (byte[])arr.Clone();
diff[^1]++;
Assert.False(arr.SequenceEqual(diff));
Assert.False(diff.SequenceEqual(arr));
}
}
}
}
}

0 comments on commit 24f1acd

Please sign in to comment.