From 74df114d08433020b79f6bd029d94886329c5f96 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 24 Feb 2021 07:19:22 -0500 Subject: [PATCH 1/3] Add new MemoryExtensions.SequenceEqual overloads --- .../System.Memory/ref/System.Memory.cs | 2 + .../tests/ReadOnlySpan/SequenceEqual.T.cs | 105 +++++++++++++++--- .../tests/ReadOnlySpan/SequenceEqual.byte.cs | 43 ++++--- .../tests/ReadOnlySpan/SequenceEqual.long.cs | 43 ++++--- .../tests/Span/SequenceEqual.T.cs | 64 ++++++++--- .../tests/Span/SequenceEqual.byte.cs | 43 ++++--- .../tests/Span/SequenceEqual.char.cs | 31 ++++-- .../tests/Span/SequenceEqual.long.cs | 43 ++++--- .../src/System/MemoryExtensions.cs | 73 +++++++++++- 9 files changed, 344 insertions(+), 103 deletions(-) diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 0cbec391795214..e90b385ca656e2 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -84,6 +84,8 @@ public static void Reverse(this System.Span span) { } public static int SequenceCompareTo(this System.Span span, System.ReadOnlySpan other) where T : System.IComparable { throw null; } public static bool SequenceEqual(this System.ReadOnlySpan span, System.ReadOnlySpan other) where T : System.IEquatable { throw null; } public static bool SequenceEqual(this System.Span span, System.ReadOnlySpan other) where T : System.IEquatable { throw null; } + public static bool SequenceEqual(this System.ReadOnlySpan span, System.ReadOnlySpan other, System.Collections.Generic.IEqualityComparer? comparer = null) { throw null; } + public static bool SequenceEqual(this System.Span span, System.ReadOnlySpan other, System.Collections.Generic.IEqualityComparer? comparer = null) { throw null; } public static void Sort(this System.Span span) { } public static void Sort(this System.Span span, System.Comparison comparison) { } public static void Sort(this System.Span keys, System.Span items) { } diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.T.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.T.cs index 5eceda493914f2..bf9aa2f4676736 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.T.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.T.cs @@ -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 @@ -14,8 +16,10 @@ public static void ZeroLengthSequenceEqual() ReadOnlySpan first = new ReadOnlySpan(a, 1, 0); ReadOnlySpan second = new ReadOnlySpan(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.Default)); } [Fact] @@ -23,8 +27,10 @@ public static void SameSpanSequenceEqual() { int[] a = { 4, 5, 6 }; ReadOnlySpan span = new ReadOnlySpan(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.Default)); } [Fact] @@ -33,12 +39,17 @@ public static void LengthMismatchSequenceEqual() int[] a = { 4, 5, 6 }; ReadOnlySpan first = new ReadOnlySpan(a, 0, 3); ReadOnlySpan second = new ReadOnlySpan(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.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++) { @@ -53,8 +64,13 @@ public static void OnSequenceEqualOfEqualSpansMakeSureEveryElementIsCompared() ReadOnlySpan firstSpan = new ReadOnlySpan(first); ReadOnlySpan secondSpan = new ReadOnlySpan(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.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 @@ -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++) { @@ -88,8 +107,13 @@ public static void SequenceEqualNoMatch() ReadOnlySpan firstSpan = new ReadOnlySpan(first); ReadOnlySpan secondSpan = new ReadOnlySpan(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.Default) + }); Assert.Equal(1, log.CountCompares(first[mismatchIndex].Value, second[mismatchIndex].Value)); } @@ -125,8 +149,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange() ReadOnlySpan firstSpan = new ReadOnlySpan(first, GuardLength, length); ReadOnlySpan secondSpan = new ReadOnlySpan(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.Default)); } } @@ -135,8 +161,53 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange() public static void SequenceEqualsNullData_String(string[] firstInput, string[] secondInput, bool expected) { ReadOnlySpan theStrings = firstInput; + Assert.Equal(expected, theStrings.SequenceEqual(secondInput)); - Assert.Equal(expected, theStrings.SequenceEqual((ReadOnlySpan)secondInput)); + Assert.Equal(expected, theStrings.SequenceEqual(secondInput, null)); + Assert.Equal(expected, theStrings.SequenceEqual(secondInput, EqualityComparer.Default)); + } + + [Fact] + public static void SequenceEqual_AlwaysTrueComparer() + { + Assert.False(((ReadOnlySpan)new int[1]).SequenceEqual(new int[2], new AlwaysComparer(true))); + Assert.True(((ReadOnlySpan)new int[2]).SequenceEqual(new int[2], new AlwaysComparer(true))); + Assert.True(((ReadOnlySpan)new int[2] { 1, 3 }).SequenceEqual(new int[2] { 2, 4 }, new AlwaysComparer(true))); + } + + [Fact] + public static void SequenceEqual_AlwaysFalseComparer() + { + Assert.False(((ReadOnlySpan)new int[1]).SequenceEqual(new int[2], new AlwaysComparer(false))); + Assert.False(((ReadOnlySpan)new int[1]).SequenceEqual(new int[2], new AlwaysComparer(false))); + Assert.False(((ReadOnlySpan)new int[2] { 1, 3 }).SequenceEqual(new int[2] { 2, 4 }, new AlwaysComparer(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)lower).SequenceEqual(lower)); + Assert.False(((ReadOnlySpan)lower).SequenceEqual(upper)); + Assert.True(((ReadOnlySpan)upper).SequenceEqual(upper)); + + Assert.True(((ReadOnlySpan)lower).SequenceEqual(lower, StringComparer.OrdinalIgnoreCase)); + Assert.True(((ReadOnlySpan)lower).SequenceEqual(upper, StringComparer.OrdinalIgnoreCase)); + Assert.True(((ReadOnlySpan)upper).SequenceEqual(upper, StringComparer.OrdinalIgnoreCase)); + + Assert.False(((ReadOnlySpan)lower).SequenceEqual(different)); + Assert.False(((ReadOnlySpan)lower).SequenceEqual(different, StringComparer.OrdinalIgnoreCase)); + } + + private sealed class AlwaysComparer : IEqualityComparer + { + 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; } } } diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.byte.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.byte.cs index 0bcd948987ab23..022681286a2d6b 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.byte.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.byte.cs @@ -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 @@ -14,8 +15,10 @@ public static void ZeroLengthSequenceEqual_Byte() ReadOnlySpan first = new ReadOnlySpan(a, 1, 0); ReadOnlySpan second = new ReadOnlySpan(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.Default)); } [Fact] @@ -23,8 +26,10 @@ public static void SameSpanSequenceEqual_Byte() { byte[] a = { 4, 5, 6 }; ReadOnlySpan span = new ReadOnlySpan(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.Default)); } [Fact] @@ -32,8 +37,10 @@ public static void SequenceEqualArrayImplicit_Byte() { byte[] a = { 4, 5, 6 }; ReadOnlySpan first = new ReadOnlySpan(a, 0, 3); - bool b = first.SequenceEqual(a); - Assert.True(b); + + Assert.True(first.SequenceEqual(a)); + Assert.True(first.SequenceEqual(a, null)); + Assert.True(first.SequenceEqual(a, EqualityComparer.Default)); } [Fact] @@ -44,8 +51,10 @@ public static void SequenceEqualArraySegmentImplicit_Byte() var segment = new ArraySegment(dst, 1, 3); ReadOnlySpan first = new ReadOnlySpan(src, 0, 3); - bool b = first.SequenceEqual(segment); - Assert.True(b); + + Assert.True(first.SequenceEqual(segment)); + Assert.True(first.SequenceEqual(segment, null)); + Assert.True(first.SequenceEqual(segment, EqualityComparer.Default)); } [Fact] @@ -54,8 +63,10 @@ public static void LengthMismatchSequenceEqual_Byte() byte[] a = { 4, 5, 6 }; ReadOnlySpan first = new ReadOnlySpan(a, 0, 3); ReadOnlySpan second = new ReadOnlySpan(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.Default)); } [Fact] @@ -76,8 +87,10 @@ public static void SequenceEqualNoMatch_Byte() ReadOnlySpan firstSpan = new ReadOnlySpan(first); ReadOnlySpan secondSpan = new ReadOnlySpan(second); - bool b = firstSpan.SequenceEqual(secondSpan); - Assert.False(b); + + Assert.False(firstSpan.SequenceEqual(secondSpan)); + Assert.False(firstSpan.SequenceEqual(secondSpan, null)); + Assert.False(firstSpan.SequenceEqual(secondSpan, EqualityComparer.Default)); } } } @@ -95,8 +108,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange_Byte() second[length + 1] = 100; ReadOnlySpan span1 = new ReadOnlySpan(first, 1, length); ReadOnlySpan span2 = new ReadOnlySpan(second, 1, length); - bool b = span1.SequenceEqual(span2); - Assert.True(b); + + Assert.True(span1.SequenceEqual(span2)); + Assert.True(span1.SequenceEqual(span2, null)); + Assert.True(span1.SequenceEqual(span2, EqualityComparer.Default)); } } } diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.long.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.long.cs index f577b645780e7a..b5ef3b1f3edc96 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.long.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.long.cs @@ -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 @@ -14,8 +15,10 @@ public static void ZeroLengthSequenceEqual_Long() ReadOnlySpan first = new ReadOnlySpan(a, 1, 0); ReadOnlySpan second = new ReadOnlySpan(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.Default)); } [Fact] @@ -23,8 +26,10 @@ public static void SameSpanSequenceEqual_Long() { long[] a = { 488238291, 52498989823, 619890289890 }; ReadOnlySpan span = new ReadOnlySpan(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.Default)); } [Fact] @@ -32,8 +37,10 @@ public static void SequenceEqualArrayImplicit_Long() { long[] a = { 488238291, 52498989823, 619890289890 }; ReadOnlySpan first = new ReadOnlySpan(a, 0, 3); - bool b = first.SequenceEqual(a); - Assert.True(b); + + Assert.True(first.SequenceEqual(a)); + Assert.True(first.SequenceEqual(a, null)); + Assert.True(first.SequenceEqual(a, EqualityComparer.Default)); } [Fact] @@ -44,8 +51,10 @@ public static void SequenceEqualArraySegmentImplicit_Long() ArraySegment segment = new ArraySegment(dst, 1, 3); ReadOnlySpan first = new ReadOnlySpan(src, 0, 3); - bool b = first.SequenceEqual(segment); - Assert.True(b); + + Assert.True(first.SequenceEqual(segment)); + Assert.True(first.SequenceEqual(segment, null)); + Assert.True(first.SequenceEqual(segment, EqualityComparer.Default)); } [Fact] @@ -54,8 +63,10 @@ public static void LengthMismatchSequenceEqual_Long() long[] a = { 488238291, 52498989823, 619890289890 }; ReadOnlySpan first = new ReadOnlySpan(a, 0, 3); ReadOnlySpan second = new ReadOnlySpan(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.Default)); } [Fact] @@ -76,8 +87,10 @@ public static void SequenceEqualNoMatch_Long() ReadOnlySpan firstSpan = new ReadOnlySpan(first); ReadOnlySpan secondSpan = new ReadOnlySpan(second); - bool b = firstSpan.SequenceEqual(secondSpan); - Assert.False(b); + + Assert.False(firstSpan.SequenceEqual(secondSpan)); + Assert.False(firstSpan.SequenceEqual(secondSpan, null)); + Assert.False(firstSpan.SequenceEqual(secondSpan, EqualityComparer.Default)); } } } @@ -95,8 +108,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange_Long() second[length + 1] = 100; ReadOnlySpan span1 = new ReadOnlySpan(first, 1, length); ReadOnlySpan span2 = new ReadOnlySpan(second, 1, length); - bool b = span1.SequenceEqual(span2); - Assert.True(b); + + Assert.True(span1.SequenceEqual(span2)); + Assert.True(span1.SequenceEqual(span2, null)); + Assert.True(span1.SequenceEqual(span2, EqualityComparer.Default)); } } } diff --git a/src/libraries/System.Memory/tests/Span/SequenceEqual.T.cs b/src/libraries/System.Memory/tests/Span/SequenceEqual.T.cs index b43814be49e5f9..75c633a6b1aa98 100644 --- a/src/libraries/System.Memory/tests/Span/SequenceEqual.T.cs +++ b/src/libraries/System.Memory/tests/Span/SequenceEqual.T.cs @@ -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 @@ -14,8 +15,10 @@ public static void ZeroLengthSequenceEqual() Span first = new Span(a, 1, 0); Span second = new Span(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.Default)); } [Fact] @@ -23,8 +26,10 @@ public static void SameSpanSequenceEqual() { int[] a = { 4, 5, 6 }; Span span = new Span(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.Default)); } [Fact] @@ -33,12 +38,17 @@ public static void LengthMismatchSequenceEqual() int[] a = { 4, 5, 6 }; Span first = new Span(a, 0, 3); Span second = new Span(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.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++) { @@ -53,8 +63,13 @@ public static void OnSequenceEqualOfEqualSpansMakeSureEveryElementIsCompared() Span firstSpan = new Span(first); ReadOnlySpan secondSpan = new ReadOnlySpan(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.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 @@ -68,8 +83,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++) { @@ -88,8 +106,13 @@ public static void SequenceEqualNoMatch() Span firstSpan = new Span(first); ReadOnlySpan secondSpan = new ReadOnlySpan(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.Default) + }); Assert.Equal(1, log.CountCompares(first[mismatchIndex].Value, second[mismatchIndex].Value)); } @@ -125,8 +148,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange() Span firstSpan = new Span(first, GuardLength, length); Span secondSpan = new Span(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.Default)); } } @@ -135,8 +160,15 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange() public static void SequenceEqualsNullData_String(string[] firstInput, string[] secondInput, bool expected) { Span theStrings = firstInput; + Assert.Equal(expected, theStrings.SequenceEqual(secondInput)); Assert.Equal(expected, theStrings.SequenceEqual((ReadOnlySpan)secondInput)); + + Assert.Equal(expected, theStrings.SequenceEqual(secondInput, null)); + Assert.Equal(expected, theStrings.SequenceEqual((ReadOnlySpan)secondInput, null)); + + Assert.Equal(expected, theStrings.SequenceEqual(secondInput, EqualityComparer.Default)); + Assert.Equal(expected, theStrings.SequenceEqual((ReadOnlySpan)secondInput, EqualityComparer.Default)); } } } diff --git a/src/libraries/System.Memory/tests/Span/SequenceEqual.byte.cs b/src/libraries/System.Memory/tests/Span/SequenceEqual.byte.cs index 67a2758ea1a2f8..eaaf813ead0db9 100644 --- a/src/libraries/System.Memory/tests/Span/SequenceEqual.byte.cs +++ b/src/libraries/System.Memory/tests/Span/SequenceEqual.byte.cs @@ -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 @@ -14,8 +15,10 @@ public static void ZeroLengthSequenceEqual_Byte() Span first = new Span(a, 1, 0); Span second = new Span(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.Default)); } [Fact] @@ -23,8 +26,10 @@ public static void SameSpanSequenceEqual_Byte() { byte[] a = { 4, 5, 6 }; Span span = new Span(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.Default)); } [Fact] @@ -32,8 +37,10 @@ public static void SequenceEqualArrayImplicit_Byte() { byte[] a = { 4, 5, 6 }; Span first = new Span(a, 0, 3); - bool b = first.SequenceEqual(a); - Assert.True(b); + + Assert.True(first.SequenceEqual(a)); + Assert.True(first.SequenceEqual(a, null)); + Assert.True(first.SequenceEqual(a, EqualityComparer.Default)); } [Fact] @@ -44,8 +51,10 @@ public static void SequenceEqualArraySegmentImplicit_Byte() var segment = new ArraySegment(dst, 1, 3); Span first = new Span(src, 0, 3); - bool b = first.SequenceEqual(segment); - Assert.True(b); + + Assert.True(first.SequenceEqual(segment)); + Assert.True(first.SequenceEqual(segment, null)); + Assert.True(first.SequenceEqual(segment, EqualityComparer.Default)); } [Fact] @@ -54,8 +63,10 @@ public static void LengthMismatchSequenceEqual_Byte() byte[] a = { 4, 5, 6 }; Span first = new Span(a, 0, 3); Span second = new Span(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.Default)); } [Fact] @@ -76,8 +87,10 @@ public static void SequenceEqualNoMatch_Byte() Span firstSpan = new Span(first); ReadOnlySpan secondSpan = new ReadOnlySpan(second); - bool b = firstSpan.SequenceEqual(secondSpan); - Assert.False(b); + + Assert.False(firstSpan.SequenceEqual(secondSpan)); + Assert.False(firstSpan.SequenceEqual(secondSpan, null)); + Assert.False(firstSpan.SequenceEqual(secondSpan, EqualityComparer.Default)); } } } @@ -95,8 +108,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange_Byte() second[length + 1] = 100; Span span1 = new Span(first, 1, length); ReadOnlySpan span2 = new ReadOnlySpan(second, 1, length); - bool b = span1.SequenceEqual(span2); - Assert.True(b); + + Assert.True(span1.SequenceEqual(span2)); + Assert.True(span1.SequenceEqual(span2, null)); + Assert.True(span1.SequenceEqual(span2, EqualityComparer.Default)); } } } diff --git a/src/libraries/System.Memory/tests/Span/SequenceEqual.char.cs b/src/libraries/System.Memory/tests/Span/SequenceEqual.char.cs index 5a13239ad5b726..60a53e66459c96 100644 --- a/src/libraries/System.Memory/tests/Span/SequenceEqual.char.cs +++ b/src/libraries/System.Memory/tests/Span/SequenceEqual.char.cs @@ -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 @@ -14,8 +15,10 @@ public static void ZeroLengthSequenceEqual_Char() Span first = new Span(a, 1, 0); Span second = new Span(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.Default)); } [Fact] @@ -23,8 +26,10 @@ public static void SameSpanSequenceEqual_Char() { char[] a = { '4', '5', '6' }; Span span = new Span(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.Default)); } [Fact] @@ -33,8 +38,10 @@ public static void LengthMismatchSequenceEqual_Char() char[] a = { '4', '5', '6' }; Span first = new Span(a, 0, 3); Span second = new Span(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.Default)); } [Fact] @@ -55,8 +62,10 @@ public static void SequenceEqualNoMatch_Char() Span firstSpan = new Span(first); ReadOnlySpan secondSpan = new ReadOnlySpan(second); - bool b = firstSpan.SequenceEqual(secondSpan); - Assert.False(b); + + Assert.False(firstSpan.SequenceEqual(secondSpan)); + Assert.False(firstSpan.SequenceEqual(secondSpan, null)); + Assert.False(firstSpan.SequenceEqual(secondSpan, EqualityComparer.Default)); } } } @@ -74,8 +83,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange_Char() second[length + 1] = 'a'; Span span1 = new Span(first, 1, length); ReadOnlySpan span2 = new ReadOnlySpan(second, 1, length); - bool b = span1.SequenceEqual(span2); - Assert.True(b); + + Assert.True(span1.SequenceEqual(span2)); + Assert.True(span1.SequenceEqual(span2, null)); + Assert.True(span1.SequenceEqual(span2, EqualityComparer.Default)); } } } diff --git a/src/libraries/System.Memory/tests/Span/SequenceEqual.long.cs b/src/libraries/System.Memory/tests/Span/SequenceEqual.long.cs index d8c86ee5ec73f0..8cfa798458f02d 100644 --- a/src/libraries/System.Memory/tests/Span/SequenceEqual.long.cs +++ b/src/libraries/System.Memory/tests/Span/SequenceEqual.long.cs @@ -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 @@ -14,8 +15,10 @@ public static void ZeroLengthSequenceEqual_Long() Span first = new Span(a, 1, 0); Span second = new Span(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.Default)); } [Fact] @@ -23,8 +26,10 @@ public static void SameSpanSequenceEqual_Long() { long[] a = { 488238291, 52498989823, 619890289890 }; Span span = new Span(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.Default)); } [Fact] @@ -32,8 +37,10 @@ public static void SequenceEqualArrayImplicit_Long() { long[] a = { 488238291, 52498989823, 619890289890 }; Span first = new Span(a, 0, 3); - bool b = first.SequenceEqual(a); - Assert.True(b); + + Assert.True(first.SequenceEqual(a)); + Assert.True(first.SequenceEqual(a, null)); + Assert.True(first.SequenceEqual(a, EqualityComparer.Default)); } [Fact] @@ -44,8 +51,10 @@ public static void SequenceEqualArraySegmentImplicit_Long() ArraySegment segment = new ArraySegment(dst, 1, 3); Span first = new Span(src, 0, 3); - bool b = first.SequenceEqual(segment); - Assert.True(b); + + Assert.True(first.SequenceEqual(segment)); + Assert.True(first.SequenceEqual(segment, null)); + Assert.True(first.SequenceEqual(segment, EqualityComparer.Default)); } [Fact] @@ -54,8 +63,10 @@ public static void LengthMismatchSequenceEqual_Long() long[] a = { 488238291, 52498989823, 619890289890 }; Span first = new Span(a, 0, 3); Span second = new Span(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.Default)); } [Fact] @@ -76,8 +87,10 @@ public static void SequenceEqualNoMatch_Long() Span firstSpan = new Span(first); ReadOnlySpan secondSpan = new ReadOnlySpan(second); - bool b = firstSpan.SequenceEqual(secondSpan); - Assert.False(b); + + Assert.False(firstSpan.SequenceEqual(secondSpan)); + Assert.False(firstSpan.SequenceEqual(secondSpan, null)); + Assert.False(firstSpan.SequenceEqual(secondSpan, EqualityComparer.Default)); } } } @@ -95,8 +108,10 @@ public static void MakeSureNoSequenceEqualChecksGoOutOfRange_Long() second[length + 1] = 100; Span span1 = new Span(first, 1, length); ReadOnlySpan span2 = new ReadOnlySpan(second, 1, length); - bool b = span1.SequenceEqual(span2); - Assert.True(b); + + Assert.True(span1.SequenceEqual(span2)); + Assert.True(span1.SequenceEqual(span2, null)); + Assert.True(span1.SequenceEqual(span2, EqualityComparer.Default)); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 2eb47a31fcc8f1..cb81baf0500406 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -909,15 +909,80 @@ public static bool SequenceEqual(this ReadOnlySpan span, ReadOnlySpan o { nuint size = (nuint)Unsafe.SizeOf(); return length == other.Length && - SpanHelpers.SequenceEqual( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - ref Unsafe.As(ref MemoryMarshal.GetReference(other)), - ((nuint)length) * size); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking. + SpanHelpers.SequenceEqual( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + ref Unsafe.As(ref MemoryMarshal.GetReference(other)), + ((nuint)length) * size); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this API in such a case so we choose not to take the overhead of checking. } return length == other.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(other), length); } + /// + /// Determines whether two sequences are equal by comparing the elements using an . + /// + /// The first sequence to compare. + /// The second sequence to compare. + /// The implementation to use when comparing elements, or null to use the default for the type of an element. + /// true if the two sequences are equal; otherwise, false. + public static bool SequenceEqual(this Span span, ReadOnlySpan other, IEqualityComparer? comparer = null) => + SequenceEqual((ReadOnlySpan)span, other, comparer); + + /// + /// Determines whether two sequences are equal by comparing the elements using an . + /// + /// The first sequence to compare. + /// The second sequence to compare. + /// The implementation to use when comparing elements, or null to use the default for the type of an element. + /// true if the two sequences are equal; otherwise, false. + public static bool SequenceEqual(this ReadOnlySpan span, ReadOnlySpan other, IEqualityComparer? comparer = null) + { + // If the spans differ in length, they're not equal. + if (span.Length != other.Length) + { + return false; + } + + if (typeof(T).IsValueType) + { + if (comparer is null || comparer == EqualityComparer.Default) + { + // If no comparer was supplied and the type is bitwise equatable, take the fast path doing a bitwise comparison. + if (RuntimeHelpers.IsBitwiseEquatable()) + { + nuint size = (nuint)Unsafe.SizeOf(); + return SpanHelpers.SequenceEqual( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + ref Unsafe.As(ref MemoryMarshal.GetReference(other)), + ((nuint)span.Length) * size); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this API in such a case so we choose not to take the overhead of checking. + } + + // Otherwise, compare each element using EqualityComparer.Default.Equals in a way that will enable it to devirtualize. + for (int i = 0; i < span.Length; i++) + { + if (!EqualityComparer.Default.Equals(span[i], other[i])) + { + return false; + } + } + + return true; + } + } + + // Use the comparer to compare each element. + comparer ??= EqualityComparer.Default; + for (int i = 0; i < span.Length; i++) + { + if (!comparer.Equals(span[i], other[i])) + { + return false; + } + } + + return true; + } + /// /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T). /// From 91e06c0a9a7a5bf55ada674d31612d3c47dce13f Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 24 Feb 2021 07:19:49 -0500 Subject: [PATCH 2/3] Use MemoryExtensions.SequenceEqual in Enumerable.SequenceEqual --- .../src/System/Linq/SequenceEqual.cs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs b/src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs index 546ce9642c8df0..d1d6dc1586b0f5 100644 --- a/src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs +++ b/src/libraries/System.Linq/src/System/Linq/SequenceEqual.cs @@ -22,26 +22,13 @@ public static bool SequenceEqual(this IEnumerable first, IEnum ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second); } - if (comparer == null) + if (first is ICollection firstCol && second is ICollection 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 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, but there's no good way without reflection to convince - // the C# compiler to let us delegate, as ReadOnlySpan.SequenceEqual requires an IEquatable - // 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)firstArr).SequenceEqual(secondArr); + return ((ReadOnlySpan)firstArray).SequenceEqual(secondArray, comparer); } - comparer = EqualityComparer.Default; - } - - if (first is ICollection firstCol && second is ICollection secondCol) - { if (firstCol.Count != secondCol.Count) { return false; @@ -49,6 +36,8 @@ public static bool SequenceEqual(this IEnumerable first, IEnum if (firstCol is IList firstList && secondCol is IList secondList) { + comparer ??= EqualityComparer.Default; + int count = firstCol.Count; for (int i = 0; i < count; i++) { @@ -65,6 +54,8 @@ public static bool SequenceEqual(this IEnumerable first, IEnum using (IEnumerator e1 = first.GetEnumerator()) using (IEnumerator e2 = second.GetEnumerator()) { + comparer ??= EqualityComparer.Default; + while (e1.MoveNext()) { if (!(e2.MoveNext() && comparer.Equals(e1.Current, e2.Current))) From 37d8c1a986c23e88d0167935d2812756fc1b5fb1 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 26 Feb 2021 10:57:28 -0500 Subject: [PATCH 3/3] Use MemoryExtensions.SequenceEqual in a few more places --- .../src/System/Data/DataKey.cs | 13 +-------- .../src/System/Data/RelatedView.cs | 17 +---------- .../tests/ArrayHelpers.cs | 16 +--------- .../src/System/TimeZoneInfo.cs | 29 ++----------------- 4 files changed, 6 insertions(+), 69 deletions(-) diff --git a/src/libraries/System.Data.Common/src/System/Data/DataKey.cs b/src/libraries/System.Data.Common/src/System/Data/DataKey.cs index df6edda565e4a7..379035bb75d6b3 100644 --- a/src/libraries/System.Data.Common/src/System/Data/DataKey.cs +++ b/src/libraries/System.Data.Common/src/System/Data/DataKey.cs @@ -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); } } diff --git a/src/libraries/System.Data.Common/src/System/Data/RelatedView.cs b/src/libraries/System.Data.Common/src/System/Data/RelatedView.cs index 3a121937972e70..590a1b8db09e69 100644 --- a/src/libraries/System.Data.Common/src/System/Data/RelatedView.cs +++ b/src/libraries/System.Data.Common/src/System/Data/RelatedView.cs @@ -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) diff --git a/src/libraries/System.IO.UnmanagedMemoryStream/tests/ArrayHelpers.cs b/src/libraries/System.IO.UnmanagedMemoryStream/tests/ArrayHelpers.cs index 3a5595f831b592..f3c8fbfa86effd 100644 --- a/src/libraries/System.IO.UnmanagedMemoryStream/tests/ArrayHelpers.cs +++ b/src/libraries/System.IO.UnmanagedMemoryStream/tests/ArrayHelpers.cs @@ -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.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) { diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs index 3c8ddb932f3489..2669b740835cae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs @@ -827,38 +827,15 @@ public bool HasSameRules(TimeZoneInfo other) return false; } - bool sameRules; AdjustmentRule[]? currentRules = _adjustmentRules; AdjustmentRule[]? otherRules = other._adjustmentRules; - sameRules = - (currentRules == null && otherRules == null) || - (currentRules != null && otherRules != null); - - if (!sameRules) + if (currentRules is null || otherRules is null) { - // AdjustmentRule array mismatch - return false; + return currentRules == otherRules; } - if (currentRules != null) - { - if (currentRules.Length != otherRules!.Length) - { - // AdjustmentRule array length mismatch - return false; - } - - for (int i = 0; i < currentRules.Length; i++) - { - if (!(currentRules[i]).Equals(otherRules[i])) - { - // AdjustmentRule value-equality mismatch - return false; - } - } - } - return sameRules; + return currentRules.AsSpan().SequenceEqual(otherRules); } ///