From afed8c1e022365656c99039c61c375ae87907980 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Thu, 15 Feb 2018 17:33:29 -0800 Subject: [PATCH 1/6] Add ROSpan ToLower and ToUpper string-like APIs with CultureInfo --- src/System.Memory/ref/System.Memory.cs | 4 + .../src/System/MemoryExtensions.Fast.cs | 45 +++ .../src/System/MemoryExtensions.Portable.cs | 71 +++++ src/System.Memory/src/System/ThrowHelper.cs | 3 +- .../tests/ReadOnlySpan/ToLower.cs | 293 ++++++++++++++++++ .../tests/ReadOnlySpan/ToUpper.cs | 279 +++++++++++++++++ .../tests/System.Memory.Tests.csproj | 2 + 7 files changed, 696 insertions(+), 1 deletion(-) create mode 100644 src/System.Memory/tests/ReadOnlySpan/ToLower.cs create mode 100644 src/System.Memory/tests/ReadOnlySpan/ToUpper.cs diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index 0a55e89d5a43..c6772399eeea 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -65,6 +65,10 @@ public static void Reverse(this System.Span span) { } public static bool SequenceEqual(this System.Span first, System.ReadOnlySpan second) where T : System.IEquatable { throw null; } public static bool StartsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } public static bool StartsWith(this System.Span span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } + public static int ToLower(this System.ReadOnlySpan source, System.Span destination, System.Globalization.CultureInfo culture) { throw null; } + public static int ToLowerInvariant(this System.ReadOnlySpan source, System.Span destination) { throw null; } + public static int ToUpper(this System.ReadOnlySpan source, System.Span destination, System.Globalization.CultureInfo culture) { throw null; } + public static int ToUpperInvariant(this System.ReadOnlySpan source, System.Span destination) { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span) { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span, char trimChar) { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span, System.ReadOnlySpan trimChars) { throw null; } diff --git a/src/System.Memory/src/System/MemoryExtensions.Fast.cs b/src/System.Memory/src/System/MemoryExtensions.Fast.cs index 2a540f43e8ba..df4a76d577e8 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Fast.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Fast.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Globalization; using System.Runtime.CompilerServices; namespace System @@ -11,6 +12,50 @@ namespace System /// public static partial class MemoryExtensions { + /// + /// Copies the characters from the source span into the destination, converting each character to lowercase, + /// using the casing rules of the specified culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + /// An object that supplies culture-specific casing rules. + /// + /// Thrown when is null. + /// + public static int ToLower(this ReadOnlySpan source, Span destination, CultureInfo culture) + => Span.ToLower(source, destination, culture); + + /// + /// Copies the characters from the source span into the destination, converting each character to lowercase, + /// using the casing rules of the invariant culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + public static int ToLowerInvariant(this ReadOnlySpan source, Span destination) + => Span.ToLowerInvariant(source, destination); + + /// + /// Copies the characters from the source span into the destination, converting each character to uppercase, + /// using the casing rules of the specified culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + /// An object that supplies culture-specific casing rules. + /// + /// Thrown when is null. + /// + public static int ToUpper(this ReadOnlySpan source, Span destination, CultureInfo culture) + => Span.ToUpper(source, destination, culture); + + /// + /// Copies the characters from the source span into the destination, converting each character to uppercase + /// using the casing rules of the invariant culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + public static int ToUpperInvariant(this ReadOnlySpan source, Span destination) + => Span.ToUpperInvariant(source, destination); + /// /// Casts a Span of one primitive type to Span of bytes. /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. diff --git a/src/System.Memory/src/System/MemoryExtensions.Portable.cs b/src/System.Memory/src/System/MemoryExtensions.Portable.cs index 8e306acf6e65..fbc6d533b25c 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.Globalization; using System.Runtime.CompilerServices; namespace System @@ -12,6 +13,76 @@ namespace System /// public static partial class MemoryExtensions { + /// + /// Copies the characters from the source span into the destination, converting each character to lowercase, + /// using the casing rules of the specified culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + /// An object that supplies culture-specific casing rules. + /// + /// Thrown when is null. + /// + public static int ToLower(this ReadOnlySpan source, Span destination, CultureInfo culture) + { + if (culture == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture); + + // Assuming that changing case does not affect length + if (destination.Length < source.Length) + return -1; + + string sourceString = source.ToString(); + string resultString = sourceString.ToLower(culture); + Debug.Assert(sourceString.Length == resultString.Length); + resultString.AsReadOnlySpan().CopyTo(destination); + return source.Length; + } + + /// + /// Copies the characters from the source span into the destination, converting each character to lowercase, + /// using the casing rules of the invariant culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + public static int ToLowerInvariant(this ReadOnlySpan source, Span destination) + => ToLower(source, destination, CultureInfo.InvariantCulture); + + /// + /// Copies the characters from the source span into the destination, converting each character to uppercase, + /// using the casing rules of the specified culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + /// An object that supplies culture-specific casing rules. + /// + /// Thrown when is null. + /// + public static int ToUpper(this ReadOnlySpan source, Span destination, CultureInfo culture) + { + if (culture == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture); + + // Assuming that changing case does not affect length + if (destination.Length < source.Length) + return -1; + + string sourceString = source.ToString(); + string resultString = sourceString.ToUpper(culture); + Debug.Assert(sourceString.Length == resultString.Length); + resultString.AsReadOnlySpan().CopyTo(destination); + return source.Length; + } + + /// + /// Copies the characters from the source span into the destination, converting each character to uppercase + /// using the casing rules of the invariant culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + public static int ToUpperInvariant(this ReadOnlySpan source, Span destination) + => ToUpper(source, destination, CultureInfo.InvariantCulture); + /// /// Casts a Span of one primitive type to Span of bytes. /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. diff --git a/src/System.Memory/src/System/ThrowHelper.cs b/src/System.Memory/src/System/ThrowHelper.cs index 32c57adcf6e5..78fc1df727da 100644 --- a/src/System.Memory/src/System/ThrowHelper.cs +++ b/src/System.Memory/src/System/ThrowHelper.cs @@ -144,6 +144,7 @@ internal enum ExceptionArgument startIndex, endIndex, array, - ownedMemory + ownedMemory, + culture } } diff --git a/src/System.Memory/tests/ReadOnlySpan/ToLower.cs b/src/System.Memory/tests/ReadOnlySpan/ToLower.cs new file mode 100644 index 000000000000..3a6114a01720 --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/ToLower.cs @@ -0,0 +1,293 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void ZeroLengthToLower() + { + char[] expectedSource = { 'a', 'B', 'c' }; + char[] a = { 'a', 'B', 'c' }; + var source = new ReadOnlySpan(a, 2, 0); + + var expectedDestination = new char[1] { 'a' }; + Span destination = new char[1] { 'a' }; + + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, a); + + source = ReadOnlySpan.Empty; + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, a); + } + + [Fact] + public static void SameSpanToLower() + { + var expected = new char[3] { 'a', 'b', 'c' }; + var a = new char[3] { 'a', 'B', 'c' }; + { + ReadOnlySpan source = a; + Span destination = a; + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expected, destination.ToArray()); + Assert.Equal(expected, source.ToArray()); + } + { + ReadOnlySpan source = a; + Span destination = a; + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expected, destination.ToArray()); + Assert.Equal(expected, source.ToArray()); + } + } + + [Fact] + public static void LengthMismatchToLower() + { + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + + var expectedDestination = new char[1] { 'a' }; + Span destination = new char[1] { 'a' }; + + Assert.Equal(-1, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(-1, source.ToLowerInvariant(destination)); + + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + + var expectedDestination = new char[4] { 'a', 'b', 'c', 'D' }; + Span destination = new char[4] { 'x', 'Y', 'z', 'D' }; + + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void ToLower() + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + var expectedDestination = new char[3] { 'a', 'b', 'c' }; + + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'x', 'Y', 'z' }; + + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'x', 'Y', 'z' }; + + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void MakeSureNoToLowerChecksGoOutOfRange() + { + for (int length = 0; length < 100; length++) + { + var first = new char[length + 2]; + var second = new char[length + 2]; + + for (int i = 0; i < first.Length; i++) + { + first[i] = 'A'; + second[i] = 'B'; + } + + first[0] = 'Z'; + first[length + 1] = 'Z'; + + second[0] = 'Y'; + second[length + 1] = 'Y'; + + var expectedSource = new char[length]; + var expectedDestination = new char[length]; + for (int i = 0; i < length; i++) + { + expectedSource[i] = 'A'; + expectedDestination[i] = 'a'; + } + + var source = new ReadOnlySpan(first, 1, length); + var destination = new Span(second, 1, length); + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + + Assert.Equal('Z', first[0]); + Assert.Equal('Z', first[length + 1]); + Assert.Equal('Y', second[0]); + Assert.Equal('Y', second[length + 1]); + } + } + + [Fact] + public static void ToLowerNullCulture() + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'a', 'B', 'c' }; + + try + { + source.ToLower(destination, null); + Assert.False(true, "Expected exception: " + typeof(ArgumentNullException).GetType()); + } + catch (ArgumentNullException) + { + } + catch (Exception wrongException) + { + Assert.False(true, "Wrong exception thrown: Expected " + typeof(ArgumentNullException).GetType() + ": Actual: " + wrongException.GetType()); + } + } + + [Theory] + [InlineData("HELLO", "hello")] + [InlineData("hello", "hello")] + [InlineData("", "")] + public static void ToLower(string s, string expected) + { + ReadOnlySpan source = s.AsReadOnlySpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expected, destination.ToString()); + } + + private static IEnumerable ToLower_Culture_TestData() + { + yield return new object[] { "H\u0049 World", "h\u0131 world", new CultureInfo("tr-TR") }; + yield return new object[] { "H\u0130 World", "h\u0069 world", new CultureInfo("tr-TR") }; + yield return new object[] { "H\u0131 World", "h\u0131 world", new CultureInfo("tr-TR") }; + + yield return new object[] { "H\u0049 World", "h\u0069 world", new CultureInfo("en-US") }; + yield return new object[] { "H\u0130 World", "h\u0069 world", new CultureInfo("en-US") }; + yield return new object[] { "H\u0131 World", "h\u0131 world", new CultureInfo("en-US") }; + + yield return new object[] { "H\u0049 World", "h\u0069 world", CultureInfo.InvariantCulture }; + yield return new object[] { "H\u0130 World", "h\u0130 world", CultureInfo.InvariantCulture }; + yield return new object[] { "H\u0131 World", "h\u0131 world", CultureInfo.InvariantCulture }; + } + + [Fact] + public static void Test_ToLower_Culture() + { + foreach (var testdata in ToLower_Culture_TestData()) + { + ToLower_Culture((string)testdata[0], (string)testdata[1], (CultureInfo)testdata[2]); + } + } + + private static void ToLower_Culture(string actual, string expected, CultureInfo culture) + { + ReadOnlySpan source = actual.AsReadOnlySpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToLower(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + + [Theory] + [InlineData("HELLO", "hello")] + [InlineData("hello", "hello")] + [InlineData("", "")] + public static void ToLowerInvariant(string s, string expected) + { + ReadOnlySpan source = s.AsReadOnlySpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expected, destination.ToString()); + } + + [Fact] + public static void ToLowerToUpperInvariant_ASCII() + { + var asciiChars = new char[128]; + var asciiCharsUpper = new char[128]; + var asciiCharsLower = new char[128]; + + for (int i = 0; i < asciiChars.Length; i++) + { + char c = (char)i; + asciiChars[i] = c; + + // Purposefully avoiding char.ToUpper/ToLower here so as not to use the same thing we're testing. + asciiCharsLower[i] = (c >= 'A' && c <= 'Z') ? (char)(c - 'A' + 'a') : c; + asciiCharsUpper[i] = (c >= 'a' && c <= 'z') ? (char)(c - 'a' + 'A') : c; + } + + ReadOnlySpan source = asciiChars; + var ascii = new string(asciiChars); + Span destinationLower = new char[source.Length]; + Span destinationUpper = new char[source.Length]; + + Assert.Equal(source.Length, source.ToLowerInvariant(destinationLower)); + Assert.Equal(source.Length, source.ToUpperInvariant(destinationUpper)); + + Assert.Equal(ascii.ToLowerInvariant(), destinationLower.ToString()); + Assert.Equal(ascii.ToUpperInvariant(), destinationUpper.ToString()); + + Assert.Equal(ascii, source.ToString()); + } + + public static IEnumerable UpperLowerCasing_TestData() + { + //lower, upper, Culture + yield return new object[] { "abcd", "ABCD", "en-US" }; + yield return new object[] { "latin i", "LATIN I", "en-US" }; + yield return new object[] { "turky \u0131", "TURKY I", "tr-TR" }; + yield return new object[] { "turky i", "TURKY \u0130", "tr-TR" }; + yield return new object[] { "\ud801\udc29", PlatformDetection.IsWindows7 ? "\ud801\udc29" : "\ud801\udc01", "en-US" }; + } + + [Theory] + [MemberData(nameof(UpperLowerCasing_TestData))] + public static void CasingTest(string lowerForm, string upperForm, string cultureName) + { + CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); + + ReadOnlySpan sourceLower = lowerForm.AsReadOnlySpan(); + ReadOnlySpan sourceUpper = upperForm.AsReadOnlySpan(); + Span destinationLower = new char[sourceUpper.Length]; + Span destinationUpper = new char[sourceLower.Length]; + + Assert.Equal(sourceUpper.Length, sourceUpper.ToLower(destinationLower, ci)); + Assert.Equal(sourceLower.Length, sourceLower.ToUpper(destinationUpper, ci)); + + Assert.Equal(upperForm.ToLower(ci), destinationLower.ToString()); + Assert.Equal(lowerForm.ToUpper(ci), destinationUpper.ToString()); + + Assert.Equal(lowerForm, sourceLower.ToString()); + Assert.Equal(upperForm, sourceUpper.ToString()); + } + } +} diff --git a/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs b/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs new file mode 100644 index 000000000000..20a8fdd30dbc --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs @@ -0,0 +1,279 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void ZeroLengthToUpper() + { + char[] expectedSource = { 'a', 'B', 'c' }; + char[] a = { 'a', 'B', 'c' }; + var source = new ReadOnlySpan(a, 2, 0); + + var expectedDestination = new char[1] { 'a' }; + Span destination = new char[1] { 'a' }; + + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, a); + + source = ReadOnlySpan.Empty; + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, a); + } + + [Fact] + public static void SameSpanToUpper() + { + var expected = new char[3] { 'A', 'B', 'C' }; + var a = new char[3] { 'a', 'B', 'c' }; + { + ReadOnlySpan source = a; + Span destination = a; + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expected, destination.ToArray()); + Assert.Equal(expected, source.ToArray()); + } + { + ReadOnlySpan source = a; + Span destination = a; + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expected, destination.ToArray()); + Assert.Equal(expected, source.ToArray()); + } + } + + [Fact] + public static void LengthMismatchToUpper() + { + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + + var expectedDestination = new char[1] { 'a' }; + Span destination = new char[1] { 'a' }; + + Assert.Equal(-1, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(-1, source.ToUpperInvariant(destination)); + + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + + var expectedDestination = new char[4] { 'A', 'B', 'C', 'd' }; + Span destination = new char[4] { 'x', 'Y', 'z', 'd' }; + + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void ToUpper() + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + var expectedDestination = new char[3] { 'A', 'B', 'C' }; + + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'x', 'Y', 'z' }; + + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'x', 'Y', 'z' }; + + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void MakeSureNoToUpperChecksGoOutOfRange() + { + for (int length = 0; length < 100; length++) + { + var first = new char[length + 2]; + var second = new char[length + 2]; + + for (int i = 0; i < first.Length; i++) + { + first[i] = 'a'; + second[i] = 'b'; + } + + first[0] = 'z'; + first[length + 1] = 'z'; + + second[0] = 'y'; + second[length + 1] = 'y'; + + var expectedSource = new char[length]; + var expectedDestination = new char[length]; + for (int i = 0; i < length; i++) + { + expectedSource[i] = 'a'; + expectedDestination[i] = 'A'; + } + + var source = new ReadOnlySpan(first, 1, length); + var destination = new Span(second, 1, length); + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + + Assert.Equal('z', first[0]); + Assert.Equal('z', first[length + 1]); + Assert.Equal('y', second[0]); + Assert.Equal('y', second[length + 1]); + } + } + + [Fact] + public static void ToUpperNullCulture() + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'a', 'B', 'c' }; + + try + { + source.ToUpper(destination, null); + Assert.False(true, "Expected exception: " + typeof(ArgumentNullException).GetType()); + } + catch (ArgumentNullException) + { + } + catch (Exception wrongException) + { + Assert.False(true, "Wrong exception thrown: Expected " + typeof(ArgumentNullException).GetType() + ": Actual: " + wrongException.GetType()); + } + } + + [Theory] + [InlineData("hello", "HELLO")] + [InlineData("HELLO", "HELLO")] + [InlineData("", "")] + public static void ToUpper(string s, string expected) + { + ReadOnlySpan source = s.AsReadOnlySpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expected, destination.ToString()); + } + + [Fact] + public static void ToUpper_TurkishI_TurkishCulture() + { + CultureInfo culture = new CultureInfo("tr-TR"); + + string s = "H\u0069 World"; + string expected = "H\u0130 WORLD"; + ReadOnlySpan source = s.AsReadOnlySpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0130 World"; + expected = "H\u0130 WORLD"; + source = s.AsReadOnlySpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0131 World"; + expected = "H\u0049 WORLD"; + source = s.AsReadOnlySpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + + [Fact] + public static void ToUpper_TurkishI_EnglishUSCulture() + { + CultureInfo culture = new CultureInfo("en-US"); + + string s = "H\u0069 World"; + string expected = "H\u0049 WORLD"; + ReadOnlySpan source = s.AsReadOnlySpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0130 World"; + expected = "H\u0130 WORLD"; + source = s.AsReadOnlySpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0131 World"; + expected = "H\u0049 WORLD"; + source = s.AsReadOnlySpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + + [Fact] + public static void ToUpper_TurkishI_InvariantCulture() + { + CultureInfo culture = CultureInfo.InvariantCulture; + + string s = "H\u0069 World"; + string expected = "H\u0049 WORLD"; + ReadOnlySpan source = s.AsReadOnlySpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0130 World"; + expected = "H\u0130 WORLD"; + source = s.AsReadOnlySpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0131 World"; + expected = "H\u0131 WORLD"; + source = s.AsReadOnlySpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + + [Theory] + [InlineData("hello", "HELLO")] + [InlineData("HELLO", "HELLO")] + [InlineData("", "")] + public static void ToUpperInvariant(string s, string expected) + { + ReadOnlySpan source = s.AsReadOnlySpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expected, destination.ToString()); + } + } +} diff --git a/src/System.Memory/tests/System.Memory.Tests.csproj b/src/System.Memory/tests/System.Memory.Tests.csproj index 119253812185..4689d7139ad6 100644 --- a/src/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/System.Memory/tests/System.Memory.Tests.csproj @@ -117,7 +117,9 @@ + + From 3131eefab18053725521cbdb98c7b7c6d93b1671 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 13:47:34 -0800 Subject: [PATCH 2/6] Add comment about overlapping spans + test. --- .../src/System/MemoryExtensions.Fast.cs | 8 +++++++ .../src/System/MemoryExtensions.Portable.cs | 8 +++++++ .../tests/ReadOnlySpan/ToLower.cs | 24 +++++++++++++++++++ .../tests/ReadOnlySpan/ToUpper.cs | 24 +++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/src/System.Memory/src/System/MemoryExtensions.Fast.cs b/src/System.Memory/src/System/MemoryExtensions.Fast.cs index df4a76d577e8..f6bca99c13c3 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Fast.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Fast.cs @@ -19,6 +19,8 @@ public static partial class MemoryExtensions /// The source span. /// The destination span which contains the transformed characters. /// An object that supplies culture-specific casing rules. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. /// /// Thrown when is null. /// @@ -31,6 +33,8 @@ public static int ToLower(this ReadOnlySpan source, Span destination /// /// The source span. /// The destination span which contains the transformed characters. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. public static int ToLowerInvariant(this ReadOnlySpan source, Span destination) => Span.ToLowerInvariant(source, destination); @@ -41,6 +45,8 @@ public static int ToLowerInvariant(this ReadOnlySpan source, Span de /// The source span. /// The destination span which contains the transformed characters. /// An object that supplies culture-specific casing rules. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. /// /// Thrown when is null. /// @@ -53,6 +59,8 @@ public static int ToUpper(this ReadOnlySpan source, Span destination /// /// The source span. /// The destination span which contains the transformed characters. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. public static int ToUpperInvariant(this ReadOnlySpan source, Span destination) => Span.ToUpperInvariant(source, destination); diff --git a/src/System.Memory/src/System/MemoryExtensions.Portable.cs b/src/System.Memory/src/System/MemoryExtensions.Portable.cs index fbc6d533b25c..4bc2f7c30a0e 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -20,6 +20,8 @@ public static partial class MemoryExtensions /// The source span. /// The destination span which contains the transformed characters. /// An object that supplies culture-specific casing rules. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. /// /// Thrown when is null. /// @@ -45,6 +47,8 @@ public static int ToLower(this ReadOnlySpan source, Span destination /// /// The source span. /// The destination span which contains the transformed characters. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. public static int ToLowerInvariant(this ReadOnlySpan source, Span destination) => ToLower(source, destination, CultureInfo.InvariantCulture); @@ -55,6 +59,8 @@ public static int ToLowerInvariant(this ReadOnlySpan source, Span de /// The source span. /// The destination span which contains the transformed characters. /// An object that supplies culture-specific casing rules. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. /// /// Thrown when is null. /// @@ -80,6 +86,8 @@ public static int ToUpper(this ReadOnlySpan source, Span destination /// /// The source span. /// The destination span which contains the transformed characters. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. public static int ToUpperInvariant(this ReadOnlySpan source, Span destination) => ToUpper(source, destination, CultureInfo.InvariantCulture); diff --git a/src/System.Memory/tests/ReadOnlySpan/ToLower.cs b/src/System.Memory/tests/ReadOnlySpan/ToLower.cs index 3a6114a01720..ade58554c17d 100644 --- a/src/System.Memory/tests/ReadOnlySpan/ToLower.cs +++ b/src/System.Memory/tests/ReadOnlySpan/ToLower.cs @@ -53,6 +53,30 @@ public static void SameSpanToLower() } } + [Fact] + public static void ToLowerOverlapping() + { + var expectedSource = new char[3] { 'B', 'c', 'b' }; + var expectedDestination = new char[3] { 'b', 'c', 'b' }; + + { + char[] a = { 'a', 'B', 'c', 'B', 'c', 'B' }; + var source = new ReadOnlySpan(a, 1, 3); + var destination = new Span(a, 3, 3); + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + { + char[] a = { 'a', 'B', 'c', 'B', 'c', 'B' }; + var source = new ReadOnlySpan(a, 1, 3); + var destination = new Span(a, 3, 3); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + [Fact] public static void LengthMismatchToLower() { diff --git a/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs b/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs index 20a8fdd30dbc..53bba6a21bb3 100644 --- a/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs +++ b/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs @@ -52,6 +52,30 @@ public static void SameSpanToUpper() } } + [Fact] + public static void ToUpperOverlapping() + { + var expectedSource = new char[3] { 'b', 'C', 'B' }; + var expectedDestination = new char[3] { 'B', 'C', 'B' }; + + { + char[] a = { 'a', 'b', 'C', 'b', 'C', 'b' }; + var source = new ReadOnlySpan(a, 1, 3); + var destination = new Span(a, 3, 3); + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + { + char[] a = { 'a', 'b', 'C', 'b', 'C', 'b' }; + var source = new ReadOnlySpan(a, 1, 3); + var destination = new Span(a, 3, 3); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + [Fact] public static void LengthMismatchToUpper() { From 7ec11003c6384494ea0d8c1913c45bfde1e5f02c Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 14:20:59 -0800 Subject: [PATCH 3/6] Respond to recent change AsReadOnlySpan -> AsSpan and use Theory --- .../src/System/MemoryExtensions.Portable.cs | 4 +- .../tests/ReadOnlySpan/ToLower.cs | 23 ++++----- .../tests/ReadOnlySpan/ToUpper.cs | 48 ++++++++++++++----- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/System.Memory/src/System/MemoryExtensions.Portable.cs b/src/System.Memory/src/System/MemoryExtensions.Portable.cs index feec6a2ac17c..1761092b4917 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -37,7 +37,7 @@ public static int ToLower(this ReadOnlySpan source, Span destination string sourceString = source.ToString(); string resultString = sourceString.ToLower(culture); Debug.Assert(sourceString.Length == resultString.Length); - resultString.AsReadOnlySpan().CopyTo(destination); + resultString.AsSpan().CopyTo(destination); return source.Length; } @@ -76,7 +76,7 @@ public static int ToUpper(this ReadOnlySpan source, Span destination string sourceString = source.ToString(); string resultString = sourceString.ToUpper(culture); Debug.Assert(sourceString.Length == resultString.Length); - resultString.AsReadOnlySpan().CopyTo(destination); + resultString.AsSpan().CopyTo(destination); return source.Length; } diff --git a/src/System.Memory/tests/ReadOnlySpan/ToLower.cs b/src/System.Memory/tests/ReadOnlySpan/ToLower.cs index ade58554c17d..d0882fd6a115 100644 --- a/src/System.Memory/tests/ReadOnlySpan/ToLower.cs +++ b/src/System.Memory/tests/ReadOnlySpan/ToLower.cs @@ -202,7 +202,7 @@ public static void ToLowerNullCulture() [InlineData("", "")] public static void ToLower(string s, string expected) { - ReadOnlySpan source = s.AsReadOnlySpan(); + ReadOnlySpan source = s.AsSpan(); Span destination = new char[source.Length]; Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); Assert.Equal(expected, destination.ToString()); @@ -223,18 +223,11 @@ private static IEnumerable ToLower_Culture_TestData() yield return new object[] { "H\u0131 World", "h\u0131 world", CultureInfo.InvariantCulture }; } - [Fact] - public static void Test_ToLower_Culture() - { - foreach (var testdata in ToLower_Culture_TestData()) - { - ToLower_Culture((string)testdata[0], (string)testdata[1], (CultureInfo)testdata[2]); - } - } - - private static void ToLower_Culture(string actual, string expected, CultureInfo culture) + [Theory] + [MemberData(nameof(ToLower_Culture_TestData))] + public static void Test_ToLower_Culture(string actual, string expected, CultureInfo culture) { - ReadOnlySpan source = actual.AsReadOnlySpan(); + ReadOnlySpan source = actual.AsSpan(); Span destination = new char[source.Length]; Assert.Equal(source.Length, source.ToLower(destination, culture)); Assert.Equal(expected, destination.ToString()); @@ -246,7 +239,7 @@ private static void ToLower_Culture(string actual, string expected, CultureInfo [InlineData("", "")] public static void ToLowerInvariant(string s, string expected) { - ReadOnlySpan source = s.AsReadOnlySpan(); + ReadOnlySpan source = s.AsSpan(); Span destination = new char[source.Length]; Assert.Equal(source.Length, source.ToLowerInvariant(destination)); Assert.Equal(expected, destination.ToString()); @@ -299,8 +292,8 @@ public static void CasingTest(string lowerForm, string upperForm, string culture { CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); - ReadOnlySpan sourceLower = lowerForm.AsReadOnlySpan(); - ReadOnlySpan sourceUpper = upperForm.AsReadOnlySpan(); + ReadOnlySpan sourceLower = lowerForm.AsSpan(); + ReadOnlySpan sourceUpper = upperForm.AsSpan(); Span destinationLower = new char[sourceUpper.Length]; Span destinationUpper = new char[sourceLower.Length]; diff --git a/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs b/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs index 53bba6a21bb3..0da84cb94fba 100644 --- a/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs +++ b/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Globalization; using Xunit; @@ -201,12 +202,37 @@ public static void ToUpperNullCulture() [InlineData("", "")] public static void ToUpper(string s, string expected) { - ReadOnlySpan source = s.AsReadOnlySpan(); + ReadOnlySpan source = s.AsSpan(); Span destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); Assert.Equal(expected, destination.ToString()); } + private static IEnumerable ToUpper_Culture_TestData() + { + yield return new object[] { "h\u0069 world", "H\u0130 WORLD", new CultureInfo("tr-TR") }; + yield return new object[] { "h\u0130 world", "H\u0130 WORLD", new CultureInfo("tr-TR") }; + yield return new object[] { "h\u0131 world", "H\u0049 WORLD", new CultureInfo("tr-TR") }; + + yield return new object[] { "h\u0069 world", "H\u0049 WORLD", new CultureInfo("en-US") }; + yield return new object[] { "h\u0130 world", "H\u0130 WORLD", new CultureInfo("en-US") }; + yield return new object[] { "h\u0131 world", "H\u0049 WORLD", new CultureInfo("en-US") }; + + yield return new object[] { "h\u0069 world", "H\u0049 WORLD", CultureInfo.InvariantCulture }; + yield return new object[] { "h\u0130 world", "H\u0130 WORLD", CultureInfo.InvariantCulture }; + yield return new object[] { "h\u0131 world", "H\u0131 WORLD", CultureInfo.InvariantCulture }; + } + + [Theory] + [MemberData(nameof(ToUpper_Culture_TestData))] + public static void Test_ToUpper_Culture(string actual, string expected, CultureInfo culture) + { + ReadOnlySpan source = actual.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + [Fact] public static void ToUpper_TurkishI_TurkishCulture() { @@ -214,21 +240,21 @@ public static void ToUpper_TurkishI_TurkishCulture() string s = "H\u0069 World"; string expected = "H\u0130 WORLD"; - ReadOnlySpan source = s.AsReadOnlySpan(); + ReadOnlySpan source = s.AsSpan(); Span destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, culture)); Assert.Equal(expected, destination.ToString()); s = "H\u0130 World"; expected = "H\u0130 WORLD"; - source = s.AsReadOnlySpan(); + source = s.AsSpan(); destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, culture)); Assert.Equal(expected, destination.ToString()); s = "H\u0131 World"; expected = "H\u0049 WORLD"; - source = s.AsReadOnlySpan(); + source = s.AsSpan(); destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, culture)); Assert.Equal(expected, destination.ToString()); @@ -241,21 +267,21 @@ public static void ToUpper_TurkishI_EnglishUSCulture() string s = "H\u0069 World"; string expected = "H\u0049 WORLD"; - ReadOnlySpan source = s.AsReadOnlySpan(); + ReadOnlySpan source = s.AsSpan(); Span destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, culture)); Assert.Equal(expected, destination.ToString()); s = "H\u0130 World"; expected = "H\u0130 WORLD"; - source = s.AsReadOnlySpan(); + source = s.AsSpan(); destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, culture)); Assert.Equal(expected, destination.ToString()); s = "H\u0131 World"; expected = "H\u0049 WORLD"; - source = s.AsReadOnlySpan(); + source = s.AsSpan(); destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, culture)); Assert.Equal(expected, destination.ToString()); @@ -268,21 +294,21 @@ public static void ToUpper_TurkishI_InvariantCulture() string s = "H\u0069 World"; string expected = "H\u0049 WORLD"; - ReadOnlySpan source = s.AsReadOnlySpan(); + ReadOnlySpan source = s.AsSpan(); Span destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, culture)); Assert.Equal(expected, destination.ToString()); s = "H\u0130 World"; expected = "H\u0130 WORLD"; - source = s.AsReadOnlySpan(); + source = s.AsSpan(); destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, culture)); Assert.Equal(expected, destination.ToString()); s = "H\u0131 World"; expected = "H\u0131 WORLD"; - source = s.AsReadOnlySpan(); + source = s.AsSpan(); destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpper(destination, culture)); Assert.Equal(expected, destination.ToString()); @@ -294,7 +320,7 @@ public static void ToUpper_TurkishI_InvariantCulture() [InlineData("", "")] public static void ToUpperInvariant(string s, string expected) { - ReadOnlySpan source = s.AsReadOnlySpan(); + ReadOnlySpan source = s.AsSpan(); Span destination = new char[source.Length]; Assert.Equal(source.Length, source.ToUpperInvariant(destination)); Assert.Equal(expected, destination.ToString()); From adedb68d1a3ab4df79757a4361207301a16c60c4 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 14:35:18 -0800 Subject: [PATCH 4/6] Add System.Globalization for netstandard1.1 --- src/System.Memory/ref/System.Memory.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Memory/ref/System.Memory.csproj b/src/System.Memory/ref/System.Memory.csproj index bb50041d827a..4b2557007595 100644 --- a/src/System.Memory/ref/System.Memory.csproj +++ b/src/System.Memory/ref/System.Memory.csproj @@ -25,6 +25,7 @@ + From 7aeb4f722d225a1d0ea37402f2c281c58b94dd1f Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 18:59:01 -0800 Subject: [PATCH 5/6] Use TextInfo.ToLower/ToUpper for netstandard1.1 --- src/System.Memory/src/System/MemoryExtensions.Portable.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/System.Memory/src/System/MemoryExtensions.Portable.cs b/src/System.Memory/src/System/MemoryExtensions.Portable.cs index 40305ad8d918..0cfbff8f37a5 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -35,7 +35,11 @@ public static int ToLower(this ReadOnlySpan source, Span destination return -1; string sourceString = source.ToString(); +#if !netstandard11 string resultString = sourceString.ToLower(culture); +#else + string resultString = culture.TextInfo.ToLower(sourceString); +#endif Debug.Assert(sourceString.Length == resultString.Length); resultString.AsSpan().CopyTo(destination); return source.Length; @@ -74,7 +78,11 @@ public static int ToUpper(this ReadOnlySpan source, Span destination return -1; string sourceString = source.ToString(); +#if !netstandard11 string resultString = sourceString.ToUpper(culture); +#else + string resultString = culture.TextInfo.ToUpper(sourceString); +#endif Debug.Assert(sourceString.Length == resultString.Length); resultString.AsSpan().CopyTo(destination); return source.Length; From e0a26daab80419c10a37646444d5155d5495d241 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 19:02:30 -0800 Subject: [PATCH 6/6] Fix XML comment --- src/System.Memory/src/System/MemoryExtensions.Fast.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/System.Memory/src/System/MemoryExtensions.Fast.cs b/src/System.Memory/src/System/MemoryExtensions.Fast.cs index 2b2f2ffb8ccb..8aba2600be9c 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Fast.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Fast.cs @@ -63,7 +63,8 @@ public static int ToUpper(this ReadOnlySpan source, Span destination /// a temporary location before the destination is overwritten. public static int ToUpperInvariant(this ReadOnlySpan source, Span destination) => Span.ToUpperInvariant(source, destination); - + + /// /// Determines whether the end of the matches the specified when compared using the specified option. /// /// The source span.