From f5ec8a49ac961472c072f7af137c42baf20f5955 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:28:38 +0000 Subject: [PATCH 1/2] Replace char.IsWhiteSpace loops with new MemoryExtensions whitespace helpers Replace manual for-loops that scan for whitespace/non-whitespace characters with calls to IndexOfAnyWhiteSpace, IndexOfAnyExceptWhiteSpace, LastIndexOfAnyExceptWhiteSpace, which use vectorized SearchValues internally. Changed: - MemoryExtensions.IsWhiteSpace - MemoryExtensions.Trim/TrimStart/TrimEnd (ReadOnlySpan and Span overloads) - MemoryExtensions.ClampStart/ClampEnd - MemoryExtensions.TrimSplitEntry - String.IsNullOrWhiteSpace - String.TrimWhiteSpaceHelper - String.MakeSeparatorListAny (whitespace case) - Guid.EatAllWhitespace - StringSegment.TrimStart/TrimEnd Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/41ef7392-01aa-4cd7-8f27-9439896279e3 Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../src/StringSegment.cs | 22 +---- .../System.Private.CoreLib/src/System/Guid.cs | 5 +- .../System/MemoryExtensions.Globalization.cs | 12 +-- .../src/System/MemoryExtensions.Trim.cs | 84 ++++--------------- .../src/System/MemoryExtensions.cs | 13 ++- .../src/System/String.Manipulation.cs | 31 +++---- .../src/System/String.cs | 7 +- 7 files changed, 41 insertions(+), 133 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs index ab49958a4fe410..71ae1abe2696fb 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs @@ -592,16 +592,8 @@ public StringSegment TrimStart() { ReadOnlySpan span = AsSpan(); - int i; - for (i = 0; i < span.Length; i++) - { - if (!char.IsWhiteSpace(span[i])) - { - break; - } - } - - return Subsegment(i); + int i = span.IndexOfAnyExceptWhiteSpace(); + return i < 0 ? Subsegment(span.Length) : Subsegment(i); } /// @@ -612,15 +604,7 @@ public StringSegment TrimEnd() { ReadOnlySpan span = AsSpan(); - int i; - for (i = span.Length - 1; i >= 0; i--) - { - if (!char.IsWhiteSpace(span[i])) - { - break; - } - } - + int i = span.LastIndexOfAnyExceptWhiteSpace(); return Subsegment(0, i + 1); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.cs index 078ceedb0b68cb..b488f144fdab09 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.cs @@ -900,9 +900,8 @@ private static ReadOnlySpan EatAllWhitespace(ReadOnlySpan s { ReadOnlySpan charSpan = Unsafe.BitCast, ReadOnlySpan>(str); // Find the first whitespace character. If there is none, just return the input. - int i; - for (i = 0; i < charSpan.Length && !char.IsWhiteSpace(charSpan[i]); i++) ; - if (i == charSpan.Length) + int i = charSpan.IndexOfAnyWhiteSpace(); + if (i < 0) { return str; } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs index c2569a96c32bc4..0928baa3e6bd9f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs @@ -14,16 +14,8 @@ public static partial class MemoryExtensions /// /// Indicates whether the specified span contains only white-space characters. /// - public static bool IsWhiteSpace(this ReadOnlySpan span) - { - for (int i = 0; i < span.Length; i++) - { - if (!char.IsWhiteSpace(span[i])) - return false; - } - - return true; - } + public static bool IsWhiteSpace(this ReadOnlySpan span) => + span.IndexOfAnyExceptWhiteSpace() < 0; /// /// Returns a value indicating whether the specified occurs within the . diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.cs index e19d7fd366d745..386407e916e258 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.cs @@ -580,23 +580,13 @@ public static ReadOnlySpan Trim(this ReadOnlySpan span) [MethodImpl(MethodImplOptions.NoInlining)] static ReadOnlySpan TrimFallback(ReadOnlySpan span) { - int start = 0; - for (; start < span.Length; start++) + int start = span.IndexOfAnyExceptWhiteSpace(); + if (start < 0) { - if (!char.IsWhiteSpace(span[start])) - { - break; - } + return default; } - int end = span.Length - 1; - for (; end > start; end--) - { - if (!char.IsWhiteSpace(span[end])) - { - break; - } - } + int end = span.LastIndexOfAnyExceptWhiteSpace(); return span.Slice(start, end - start + 1); } } @@ -607,16 +597,8 @@ static ReadOnlySpan TrimFallback(ReadOnlySpan span) /// The source span from which the characters are removed. public static ReadOnlySpan TrimStart(this ReadOnlySpan span) { - int start = 0; - for (; start < span.Length; start++) - { - if (!char.IsWhiteSpace(span[start])) - { - break; - } - } - - return span.Slice(start); + int start = span.IndexOfAnyExceptWhiteSpace(); + return start < 0 ? default : span.Slice(start); } /// @@ -625,15 +607,7 @@ public static ReadOnlySpan TrimStart(this ReadOnlySpan span) /// The source span from which the characters are removed. public static ReadOnlySpan TrimEnd(this ReadOnlySpan span) { - int end = span.Length - 1; - for (; end >= 0; end--) - { - if (!char.IsWhiteSpace(span[end])) - { - break; - } - } - + int end = span.LastIndexOfAnyExceptWhiteSpace(); return span.Slice(0, end + 1); } @@ -797,23 +771,13 @@ public static Span Trim(this Span span) [MethodImpl(MethodImplOptions.NoInlining)] static Span TrimFallback(Span span) { - int start = 0; - for (; start < span.Length; start++) + int start = ((ReadOnlySpan)span).IndexOfAnyExceptWhiteSpace(); + if (start < 0) { - if (!char.IsWhiteSpace(span[start])) - { - break; - } + return default; } - int end = span.Length - 1; - for (; end > start; end--) - { - if (!char.IsWhiteSpace(span[end])) - { - break; - } - } + int end = ((ReadOnlySpan)span).LastIndexOfAnyExceptWhiteSpace(); return span.Slice(start, end - start + 1); } } @@ -838,17 +802,8 @@ public static Span TrimEnd(this Span span) /// The source span from which the characters are removed. private static int ClampStart(ReadOnlySpan span) { - int start = 0; - - for (; start < span.Length; start++) - { - if (!char.IsWhiteSpace(span[start])) - { - break; - } - } - - return start; + int start = span.IndexOfAnyExceptWhiteSpace(); + return start < 0 ? span.Length : start; } /// @@ -861,17 +816,8 @@ private static int ClampEnd(ReadOnlySpan span, int start) // Initially, start==len==0. If ClampStart trims all, start==len Debug.Assert((uint)start <= span.Length); - int end = span.Length - 1; - - for (; end >= start; end--) - { - if (!char.IsWhiteSpace(span[end])) - { - break; - } - } - - return end - start + 1; + int end = span.Slice(start).LastIndexOfAnyExceptWhiteSpace(); + return end + 1; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 9b3b46e2949b85..d2b541843ad8f2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -5395,17 +5395,16 @@ private static int SplitCore( /// Updates the starting and ending markers for a range to exclude whitespace. private static (int StartInclusive, int EndExclusive) TrimSplitEntry(ReadOnlySpan source, int startInclusive, int endExclusive) { - while (startInclusive < endExclusive && char.IsWhiteSpace(source[startInclusive])) - { - startInclusive++; - } + ReadOnlySpan slice = source.Slice(startInclusive, endExclusive - startInclusive); - while (endExclusive > startInclusive && char.IsWhiteSpace(source[endExclusive - 1])) + int startOffset = slice.IndexOfAnyExceptWhiteSpace(); + if (startOffset < 0) { - endExclusive--; + return (endExclusive, endExclusive); } - return (startInclusive, endExclusive); + int endOffset = slice.LastIndexOfAnyExceptWhiteSpace(); + return (startInclusive + startOffset, startInclusive + endOffset + 1); } /// Counts the number of times the specified occurs in the . diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index 343cf7bc9734d8..cb9003d613ed2b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs @@ -2035,12 +2035,15 @@ internal static void MakeSeparatorListAny(ReadOnlySpan source, ReadOnlySpa // Special-case no separators to mean any whitespace is a separator. if (separators.Length == 0) { - for (int i = 0; i < source.Length; i++) + int offset = 0; + ReadOnlySpan remaining = source; + int i = remaining.IndexOfAnyWhiteSpace(); + while ((uint)i < (uint)remaining.Length) { - if (char.IsWhiteSpace(source[i])) - { - sepListBuilder.Append(i); - } + sepListBuilder.Append(offset + i); + remaining = remaining.Slice(i + 1); + offset += i + 1; + i = remaining.IndexOfAnyWhiteSpace(); } } @@ -2639,24 +2642,14 @@ private string TrimWhiteSpaceHelper(TrimType trimType) // Trim specified characters. if ((trimType & TrimType.Head) != 0) { - for (start = 0; start < Length; start++) - { - if (!char.IsWhiteSpace(this[start])) - { - break; - } - } + int idx = ((ReadOnlySpan)this).IndexOfAnyExceptWhiteSpace(); + start = idx < 0 ? Length : idx; } if ((trimType & TrimType.Tail) != 0) { - for (end = Length - 1; end >= start; end--) - { - if (!char.IsWhiteSpace(this[end])) - { - break; - } - } + int idx = ((ReadOnlySpan)this).Slice(start).LastIndexOfAnyExceptWhiteSpace(); + end = start + idx; } return CreateTrimmedString(start, end); diff --git a/src/libraries/System.Private.CoreLib/src/System/String.cs b/src/libraries/System.Private.CoreLib/src/System/String.cs index 8121cab2050dfc..7c9a686093b852 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.cs @@ -520,12 +520,7 @@ public static bool IsNullOrWhiteSpace([NotNullWhen(false)] string? value) { if (value == null) return true; - for (int i = 0; i < value.Length; i++) - { - if (!char.IsWhiteSpace(value[i])) return false; - } - - return true; + return ((ReadOnlySpan)value).IndexOfAnyExceptWhiteSpace() < 0; } /// From 17793886f76b1d243af03dc04c2aab3d5ef548a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:13:09 +0000 Subject: [PATCH 2/2] Revert all changes except Guid.EatAllWhitespace Keep only the IndexOfAnyWhiteSpace change in Guid.EatAllWhitespace, reverting all other replacements as requested. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/2e3e7d3b-f6ce-480c-a749-c51bd5124c9f Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../src/StringSegment.cs | 22 ++++- .../System/MemoryExtensions.Globalization.cs | 12 ++- .../src/System/MemoryExtensions.Trim.cs | 84 +++++++++++++++---- .../src/System/MemoryExtensions.cs | 13 +-- .../src/System/String.Manipulation.cs | 31 ++++--- .../src/System/String.cs | 7 +- 6 files changed, 130 insertions(+), 39 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs index 71ae1abe2696fb..ab49958a4fe410 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs @@ -592,8 +592,16 @@ public StringSegment TrimStart() { ReadOnlySpan span = AsSpan(); - int i = span.IndexOfAnyExceptWhiteSpace(); - return i < 0 ? Subsegment(span.Length) : Subsegment(i); + int i; + for (i = 0; i < span.Length; i++) + { + if (!char.IsWhiteSpace(span[i])) + { + break; + } + } + + return Subsegment(i); } /// @@ -604,7 +612,15 @@ public StringSegment TrimEnd() { ReadOnlySpan span = AsSpan(); - int i = span.LastIndexOfAnyExceptWhiteSpace(); + int i; + for (i = span.Length - 1; i >= 0; i--) + { + if (!char.IsWhiteSpace(span[i])) + { + break; + } + } + return Subsegment(0, i + 1); } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs index 0928baa3e6bd9f..c2569a96c32bc4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs @@ -14,8 +14,16 @@ public static partial class MemoryExtensions /// /// Indicates whether the specified span contains only white-space characters. /// - public static bool IsWhiteSpace(this ReadOnlySpan span) => - span.IndexOfAnyExceptWhiteSpace() < 0; + public static bool IsWhiteSpace(this ReadOnlySpan span) + { + for (int i = 0; i < span.Length; i++) + { + if (!char.IsWhiteSpace(span[i])) + return false; + } + + return true; + } /// /// Returns a value indicating whether the specified occurs within the . diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.cs index 386407e916e258..e19d7fd366d745 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.cs @@ -580,13 +580,23 @@ public static ReadOnlySpan Trim(this ReadOnlySpan span) [MethodImpl(MethodImplOptions.NoInlining)] static ReadOnlySpan TrimFallback(ReadOnlySpan span) { - int start = span.IndexOfAnyExceptWhiteSpace(); - if (start < 0) + int start = 0; + for (; start < span.Length; start++) { - return default; + if (!char.IsWhiteSpace(span[start])) + { + break; + } } - int end = span.LastIndexOfAnyExceptWhiteSpace(); + int end = span.Length - 1; + for (; end > start; end--) + { + if (!char.IsWhiteSpace(span[end])) + { + break; + } + } return span.Slice(start, end - start + 1); } } @@ -597,8 +607,16 @@ static ReadOnlySpan TrimFallback(ReadOnlySpan span) /// The source span from which the characters are removed. public static ReadOnlySpan TrimStart(this ReadOnlySpan span) { - int start = span.IndexOfAnyExceptWhiteSpace(); - return start < 0 ? default : span.Slice(start); + int start = 0; + for (; start < span.Length; start++) + { + if (!char.IsWhiteSpace(span[start])) + { + break; + } + } + + return span.Slice(start); } /// @@ -607,7 +625,15 @@ public static ReadOnlySpan TrimStart(this ReadOnlySpan span) /// The source span from which the characters are removed. public static ReadOnlySpan TrimEnd(this ReadOnlySpan span) { - int end = span.LastIndexOfAnyExceptWhiteSpace(); + int end = span.Length - 1; + for (; end >= 0; end--) + { + if (!char.IsWhiteSpace(span[end])) + { + break; + } + } + return span.Slice(0, end + 1); } @@ -771,13 +797,23 @@ public static Span Trim(this Span span) [MethodImpl(MethodImplOptions.NoInlining)] static Span TrimFallback(Span span) { - int start = ((ReadOnlySpan)span).IndexOfAnyExceptWhiteSpace(); - if (start < 0) + int start = 0; + for (; start < span.Length; start++) { - return default; + if (!char.IsWhiteSpace(span[start])) + { + break; + } } - int end = ((ReadOnlySpan)span).LastIndexOfAnyExceptWhiteSpace(); + int end = span.Length - 1; + for (; end > start; end--) + { + if (!char.IsWhiteSpace(span[end])) + { + break; + } + } return span.Slice(start, end - start + 1); } } @@ -802,8 +838,17 @@ public static Span TrimEnd(this Span span) /// The source span from which the characters are removed. private static int ClampStart(ReadOnlySpan span) { - int start = span.IndexOfAnyExceptWhiteSpace(); - return start < 0 ? span.Length : start; + int start = 0; + + for (; start < span.Length; start++) + { + if (!char.IsWhiteSpace(span[start])) + { + break; + } + } + + return start; } /// @@ -816,8 +861,17 @@ private static int ClampEnd(ReadOnlySpan span, int start) // Initially, start==len==0. If ClampStart trims all, start==len Debug.Assert((uint)start <= span.Length); - int end = span.Slice(start).LastIndexOfAnyExceptWhiteSpace(); - return end + 1; + int end = span.Length - 1; + + for (; end >= start; end--) + { + if (!char.IsWhiteSpace(span[end])) + { + break; + } + } + + return end - start + 1; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index d2b541843ad8f2..9b3b46e2949b85 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -5395,16 +5395,17 @@ private static int SplitCore( /// Updates the starting and ending markers for a range to exclude whitespace. private static (int StartInclusive, int EndExclusive) TrimSplitEntry(ReadOnlySpan source, int startInclusive, int endExclusive) { - ReadOnlySpan slice = source.Slice(startInclusive, endExclusive - startInclusive); + while (startInclusive < endExclusive && char.IsWhiteSpace(source[startInclusive])) + { + startInclusive++; + } - int startOffset = slice.IndexOfAnyExceptWhiteSpace(); - if (startOffset < 0) + while (endExclusive > startInclusive && char.IsWhiteSpace(source[endExclusive - 1])) { - return (endExclusive, endExclusive); + endExclusive--; } - int endOffset = slice.LastIndexOfAnyExceptWhiteSpace(); - return (startInclusive + startOffset, startInclusive + endOffset + 1); + return (startInclusive, endExclusive); } /// Counts the number of times the specified occurs in the . diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index cb9003d613ed2b..343cf7bc9734d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs @@ -2035,15 +2035,12 @@ internal static void MakeSeparatorListAny(ReadOnlySpan source, ReadOnlySpa // Special-case no separators to mean any whitespace is a separator. if (separators.Length == 0) { - int offset = 0; - ReadOnlySpan remaining = source; - int i = remaining.IndexOfAnyWhiteSpace(); - while ((uint)i < (uint)remaining.Length) + for (int i = 0; i < source.Length; i++) { - sepListBuilder.Append(offset + i); - remaining = remaining.Slice(i + 1); - offset += i + 1; - i = remaining.IndexOfAnyWhiteSpace(); + if (char.IsWhiteSpace(source[i])) + { + sepListBuilder.Append(i); + } } } @@ -2642,14 +2639,24 @@ private string TrimWhiteSpaceHelper(TrimType trimType) // Trim specified characters. if ((trimType & TrimType.Head) != 0) { - int idx = ((ReadOnlySpan)this).IndexOfAnyExceptWhiteSpace(); - start = idx < 0 ? Length : idx; + for (start = 0; start < Length; start++) + { + if (!char.IsWhiteSpace(this[start])) + { + break; + } + } } if ((trimType & TrimType.Tail) != 0) { - int idx = ((ReadOnlySpan)this).Slice(start).LastIndexOfAnyExceptWhiteSpace(); - end = start + idx; + for (end = Length - 1; end >= start; end--) + { + if (!char.IsWhiteSpace(this[end])) + { + break; + } + } } return CreateTrimmedString(start, end); diff --git a/src/libraries/System.Private.CoreLib/src/System/String.cs b/src/libraries/System.Private.CoreLib/src/System/String.cs index 7c9a686093b852..8121cab2050dfc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.cs @@ -520,7 +520,12 @@ public static bool IsNullOrWhiteSpace([NotNullWhen(false)] string? value) { if (value == null) return true; - return ((ReadOnlySpan)value).IndexOfAnyExceptWhiteSpace() < 0; + for (int i = 0; i < value.Length; i++) + { + if (!char.IsWhiteSpace(value[i])) return false; + } + + return true; } ///