diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs index 7b09ad2c063434..111be812c1ff82 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs @@ -259,13 +259,13 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex info.AddValue(ThreadIDField, 0); } - [System.Diagnostics.Conditional("DEBUG")] + [Conditional("DEBUG")] private void AssertInvariants() { Debug.Assert(m_ChunkOffset + m_ChunkChars.Length >= m_ChunkOffset, "The length of the string is greater than int.MaxValue."); StringBuilder currentBlock = this; - int maxCapacity = this.m_MaxCapacity; + int maxCapacity = m_MaxCapacity; while (true) { // All blocks have the same max capacity. @@ -388,7 +388,7 @@ ref MemoryMarshal.GetArrayDataReference(sourceArray), /// The number of characters to read in this builder. public string ToString(int startIndex, int length) { - int currentLength = this.Length; + int currentLength = Length; if (startIndex < 0) { throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex); @@ -408,19 +408,13 @@ public string ToString(int startIndex, int length) AssertInvariants(); string result = string.FastAllocateString(length); - unsafe - { - fixed (char* destinationPtr = result) - { - this.CopyTo(startIndex, new Span(destinationPtr, length), length); - return result; - } - } + CopyTo(startIndex, new Span(ref result.GetRawStringData(), length), length); + return result; } public StringBuilder Clear() { - this.Length = 0; + Length = 0; return this; } @@ -690,7 +684,7 @@ public ManyChunkInfo(StringBuilder? stringBuilder, int chunkCount) _chunkPos = -1; } } -#endregion + #endregion } /// @@ -776,14 +770,8 @@ public StringBuilder Append(char[]? value, int startIndex, int charCount) return this; } - unsafe - { - fixed (char* valueChars = &value[startIndex]) - { - Append(valueChars, charCount); - return this; - } - } + Append(value.AsSpan(startIndex, charCount)); + return this; } /// @@ -794,7 +782,7 @@ public StringBuilder Append(string? value) { if (value != null) { - // We could have just called AppendHelper here; this is a hand-specialization of that code. + // We could have just appended the span here; this is a hand-specialization of that code. char[] chunkChars = m_ChunkChars; int chunkLength = m_ChunkLength; int valueLen = value.Length; @@ -824,26 +812,13 @@ ref value.GetRawStringData(), } else { - AppendHelper(value); + Append(value.AsSpan()); } } return this; } - // We put this fixed in its own helper to avoid the cost of zero-initing `valueChars` in the - // case we don't actually use it. - private void AppendHelper(string value) - { - unsafe - { - fixed (char* valueChars = value) - { - Append(valueChars, value.Length); - } - } - } - /// /// Appends part of a string to the end of this builder. /// @@ -880,14 +855,8 @@ public StringBuilder Append(string? value, int startIndex, int count) throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index); } - unsafe - { - fixed (char* valueChars = value) - { - Append(valueChars + startIndex, count); - return this; - } - } + Append(value.AsSpan(startIndex, count)); + return this; } public StringBuilder Append(StringBuilder? value) @@ -1045,7 +1014,9 @@ public void CopyTo(int sourceIndex, Span destination, int count) /// The index to insert in this builder. /// The string to insert. /// The number of times to insert the string. - public StringBuilder Insert(int index, string? value, int count) + public StringBuilder Insert(int index, string? value, int count) => Insert(index, value.AsSpan(), count); + + private StringBuilder Insert(int index, ReadOnlySpan value, int count) { if (count < 0) { @@ -1058,7 +1029,7 @@ public StringBuilder Insert(int index, string? value, int count) throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); } - if (string.IsNullOrEmpty(value) || count == 0) + if (value.IsEmpty || count == 0) { return this; } @@ -1066,26 +1037,20 @@ public StringBuilder Insert(int index, string? value, int count) // Ensure we don't insert more chars than we can hold, and we don't // have any integer overflow in our new length. long insertingChars = (long)value.Length * count; - if (insertingChars > MaxCapacity - this.Length) + if (insertingChars > MaxCapacity - currentLength) { throw new OutOfMemoryException(); } - Debug.Assert(insertingChars + this.Length < int.MaxValue); + Debug.Assert(insertingChars + currentLength < int.MaxValue); MakeRoom(index, (int)insertingChars, out StringBuilder chunk, out int indexInChunk, false); - unsafe + while (count > 0) { - fixed (char* valuePtr = value) - { - while (count > 0) - { - ReplaceInPlaceAtChunk(ref chunk!, ref indexInChunk, valuePtr, value.Length); - --count; - } - - return this; - } + ReplaceInPlaceAtChunk(ref chunk!, ref indexInChunk, value); + --count; } + + return this; } /// @@ -1201,33 +1166,43 @@ internal StringBuilder AppendSpanFormattable(T value, string? format, IFormat public StringBuilder Append(object? value) => (value == null) ? this : Append(value.ToString()); - public StringBuilder Append(char[]? value) + public StringBuilder Append(char[]? value) => Append(value.AsSpan()); + + public StringBuilder Append(ReadOnlySpan value) { - if (value?.Length > 0) + // This case is so common we want to optimize for it heavily. + if (value.TryCopyTo(m_ChunkChars.AsSpan(m_ChunkLength))) { - unsafe + m_ChunkLength += value.Length; + } + else + { + // This is where we can check if adding value will put us over m_MaxCapacity. + // We are doing the check here to prevent the corruption of the StringBuilder. + int newLength = Length + value.Length; + if (newLength > m_MaxCapacity || newLength < value.Length) { - fixed (char* valueChars = &value[0]) - { - Append(valueChars, value.Length); - } + throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_LengthGreaterThanCapacity); } - } - return this; - } - public StringBuilder Append(ReadOnlySpan value) - { - if (value.Length > 0) - { - unsafe + // Copy the first chunk + int firstLength = m_ChunkChars.Length - m_ChunkLength; + if (firstLength > 0) { - fixed (char* valueChars = &MemoryMarshal.GetReference(value)) - { - Append(valueChars, value.Length); - } + value.Slice(0, firstLength).CopyTo(m_ChunkChars.AsSpan(m_ChunkLength)); + m_ChunkLength = m_ChunkChars.Length; } + + // Expand the builder to add another chunk. + int restLength = value.Length - firstLength; + ExpandByABlock(restLength); + Debug.Assert(m_ChunkLength == 0, "A new block was not created."); + + // Copy the second chunk + value.Slice(firstLength).CopyTo(m_ChunkChars); + m_ChunkLength = restLength; } + AssertInvariants(); return this; } @@ -1257,53 +1232,26 @@ public StringBuilder Append(ReadOnlySpan value) #region AppendJoin - public unsafe StringBuilder AppendJoin(string? separator, params object?[] values) - { - separator ??= string.Empty; - fixed (char* pSeparator = separator) - { - return AppendJoinCore(pSeparator, separator.Length, values); - } - } + public StringBuilder AppendJoin(string? separator, params object?[] values) => + AppendJoinCore(separator.AsSpan(), values); - public unsafe StringBuilder AppendJoin(string? separator, IEnumerable values) - { - separator ??= string.Empty; - fixed (char* pSeparator = separator) - { - return AppendJoinCore(pSeparator, separator.Length, values); - } - } + public StringBuilder AppendJoin(string? separator, IEnumerable values) => + AppendJoinCore(separator.AsSpan(), values); - public unsafe StringBuilder AppendJoin(string? separator, params string?[] values) - { - separator ??= string.Empty; - fixed (char* pSeparator = separator) - { - return AppendJoinCore(pSeparator, separator.Length, values); - } - } + public StringBuilder AppendJoin(string? separator, params string?[] values) => + AppendJoinCore(separator.AsSpan(), values); - public unsafe StringBuilder AppendJoin(char separator, params object?[] values) - { - return AppendJoinCore(&separator, 1, values); - } + public StringBuilder AppendJoin(char separator, params object?[] values) => + AppendJoinCore(new ReadOnlySpan(ref separator, 1), values); - public unsafe StringBuilder AppendJoin(char separator, IEnumerable values) - { - return AppendJoinCore(&separator, 1, values); - } + public StringBuilder AppendJoin(char separator, IEnumerable values) => + AppendJoinCore(new ReadOnlySpan(ref separator, 1), values); - public unsafe StringBuilder AppendJoin(char separator, params string?[] values) - { - return AppendJoinCore(&separator, 1, values); - } + public StringBuilder AppendJoin(char separator, params string?[] values) => + AppendJoinCore(new ReadOnlySpan(ref separator, 1), values); - private unsafe StringBuilder AppendJoinCore(char* separator, int separatorLength, IEnumerable values) + private StringBuilder AppendJoinCore(ReadOnlySpan separator, IEnumerable values) { - Debug.Assert(separator != null); - Debug.Assert(separatorLength >= 0); - if (values == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values); @@ -1325,7 +1273,7 @@ private unsafe StringBuilder AppendJoinCore(char* separator, int separatorLen while (en.MoveNext()) { - Append(separator, separatorLength); + Append(separator); value = en.Current; if (value != null) { @@ -1336,7 +1284,7 @@ private unsafe StringBuilder AppendJoinCore(char* separator, int separatorLen return this; } - private unsafe StringBuilder AppendJoinCore(char* separator, int separatorLength, T[] values) + private StringBuilder AppendJoinCore(ReadOnlySpan separator, T[] values) { if (values == null) { @@ -1356,7 +1304,7 @@ private unsafe StringBuilder AppendJoinCore(char* separator, int separatorLen for (int i = 1; i < values.Length; i++) { - Append(separator, separatorLength); + Append(separator); if (values[i] != null) { Append(values[i]!.ToString()); @@ -1367,55 +1315,22 @@ private unsafe StringBuilder AppendJoinCore(char* separator, int separatorLen #endregion - public StringBuilder Insert(int index, string? value) - { - if ((uint)index > (uint)Length) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); - } - - if (value != null) - { - unsafe - { - fixed (char* sourcePtr = value) - Insert(index, sourcePtr, value.Length); - } - } - return this; - } + public StringBuilder Insert(int index, string? value) => Insert(index, value.AsSpan()); - public StringBuilder Insert(int index, bool value) => Insert(index, value.ToString(), 1); +#pragma warning disable CA1830 // Prefer strongly-typed Append and Insert method overloads on StringBuilder. No need to fix for the builder itself + public StringBuilder Insert(int index, bool value) => Insert(index, value.ToString().AsSpan(), 1); +#pragma warning restore CA1830 [CLSCompliant(false)] - public StringBuilder Insert(int index, sbyte value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, sbyte value) => InsertSpanFormattable(index, value); - public StringBuilder Insert(int index, byte value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, byte value) => InsertSpanFormattable(index, value); - public StringBuilder Insert(int index, short value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, short value) => InsertSpanFormattable(index, value); - public StringBuilder Insert(int index, char value) - { - unsafe - { - Insert(index, &value, 1); - } - return this; - } - - public StringBuilder Insert(int index, char[]? value) - { - if ((uint)index > (uint)Length) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); - } + public StringBuilder Insert(int index, char value) => Insert(index, new ReadOnlySpan(ref value, 1)); - if (value != null) - { - Insert(index, value, 0, value.Length); - } - return this; - } + public StringBuilder Insert(int index, char[]? value) => Insert(index, value.AsSpan()); public StringBuilder Insert(int index, char[]? value, int startIndex, int charCount) { @@ -1449,37 +1364,29 @@ public StringBuilder Insert(int index, char[]? value, int startIndex, int charCo throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index); } - if (charCount > 0) - { - unsafe - { - fixed (char* sourcePtr = &value[startIndex]) - Insert(index, sourcePtr, charCount); - } - } - return this; + return Insert(index, value.AsSpan(startIndex, charCount)); } - public StringBuilder Insert(int index, int value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, int value) => InsertSpanFormattable(index, value); - public StringBuilder Insert(int index, long value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, long value) => InsertSpanFormattable(index, value); - public StringBuilder Insert(int index, float value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, float value) => InsertSpanFormattable(index, value); - public StringBuilder Insert(int index, double value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, double value) => InsertSpanFormattable(index, value); - public StringBuilder Insert(int index, decimal value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, decimal value) => InsertSpanFormattable(index, value); [CLSCompliant(false)] - public StringBuilder Insert(int index, ushort value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, ushort value) => InsertSpanFormattable(index, value); [CLSCompliant(false)] - public StringBuilder Insert(int index, uint value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, uint value) => InsertSpanFormattable(index, value); [CLSCompliant(false)] - public StringBuilder Insert(int index, ulong value) => Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, ulong value) => InsertSpanFormattable(index, value); - public StringBuilder Insert(int index, object? value) => (value == null) ? this : Insert(index, value.ToString(), 1); + public StringBuilder Insert(int index, object? value) => (value == null) ? this : Insert(index, value.ToString().AsSpan(), 1); public StringBuilder Insert(int index, ReadOnlySpan value) { @@ -1488,17 +1395,29 @@ public StringBuilder Insert(int index, ReadOnlySpan value) throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); } - if (value.Length > 0) + if (!value.IsEmpty) { - unsafe - { - fixed (char* sourcePtr = &MemoryMarshal.GetReference(value)) - Insert(index, sourcePtr, value.Length); - } + MakeRoom(index, value.Length, out StringBuilder chunk, out int indexInChunk, false); + ReplaceInPlaceAtChunk(ref chunk!, ref indexInChunk, value); } return this; } + private StringBuilder InsertSpanFormattable(int index, T value) where T : ISpanFormattable + { + Debug.Assert(typeof(T).Assembly.Equals(typeof(object).Assembly), "Implementation trusts the results of TryFormat because T is expected to be something known"); + + Span buffer = stackalloc char[256]; + if (value.TryFormat(buffer, out int charsWritten, format: default, provider: null)) + { + // We don't use Insert(int, ReadOnlySpan) for exception compatibility; + // we want exceeding the maximum capacity to throw an OutOfMemoryException. + return Insert(index, buffer.Slice(0, charsWritten), 1); + } + + return Insert(index, value.ToString().AsSpan(), 1); + } + public StringBuilder AppendFormat(string format, object? arg0) => AppendFormatHelper(null, format, new ParamsArray(arg0)); public StringBuilder AppendFormat(string format, object? arg0, object? arg1) => AppendFormatHelper(null, format, new ParamsArray(arg0, arg1)); @@ -1758,7 +1677,7 @@ internal StringBuilder AppendFormatHelper(IFormatProvider? provider, string form if (cf != null) { - if (itemFormatSpan.Length != 0) + if (!itemFormatSpan.IsEmpty) { itemFormat = new string(itemFormatSpan); } @@ -1797,7 +1716,7 @@ internal StringBuilder AppendFormatHelper(IFormatProvider? provider, string form // Otherwise, fallback to trying IFormattable or calling ToString. if (arg is IFormattable formattableArg) { - if (itemFormatSpan.Length != 0) + if (!itemFormatSpan.IsEmpty) { itemFormat ??= new string(itemFormatSpan); } @@ -2100,64 +2019,10 @@ public unsafe StringBuilder Append(char* value, int valueCount) throw new ArgumentOutOfRangeException(nameof(valueCount), SR.ArgumentOutOfRange_NegativeCount); } - // this is where we can check if the valueCount will put us over m_MaxCapacity - // We are doing the check here to prevent the corruption of the StringBuilder. - int newLength = Length + valueCount; - if (newLength > m_MaxCapacity || newLength < valueCount) - { - throw new ArgumentOutOfRangeException(nameof(valueCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity); - } - - // This case is so common we want to optimize for it heavily. - int newIndex = valueCount + m_ChunkLength; - if (newIndex <= m_ChunkChars.Length) - { - new ReadOnlySpan(value, valueCount).CopyTo(m_ChunkChars.AsSpan(m_ChunkLength)); - m_ChunkLength = newIndex; - } - else - { - // Copy the first chunk - int firstLength = m_ChunkChars.Length - m_ChunkLength; - if (firstLength > 0) - { - new ReadOnlySpan(value, firstLength).CopyTo(m_ChunkChars.AsSpan(m_ChunkLength)); - m_ChunkLength = m_ChunkChars.Length; - } - - // Expand the builder to add another chunk. - int restLength = valueCount - firstLength; - ExpandByABlock(restLength); - Debug.Assert(m_ChunkLength == 0, "A new block was not created."); - - // Copy the second chunk - new ReadOnlySpan(value + firstLength, restLength).CopyTo(m_ChunkChars); - m_ChunkLength = restLength; - } - AssertInvariants(); + Append(new ReadOnlySpan(value, valueCount)); return this; } - /// - /// Inserts a character buffer into this builder at the specified position. - /// - /// The index to insert in this builder. - /// The pointer to the start of the buffer. - /// The number of characters in the buffer. - private unsafe void Insert(int index, char* value, int valueCount) - { - if ((uint)index > (uint)Length) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); - } - - if (valueCount > 0) - { - MakeRoom(index, valueCount, out StringBuilder chunk, out int indexInChunk, false); - ReplaceInPlaceAtChunk(ref chunk!, ref indexInChunk, value, valueCount); - } - } - /// /// Replaces strings at specified indices with a new string in a chunk. /// @@ -2176,65 +2041,58 @@ private void ReplaceAllInChunk(int[]? replacements, int replacementsCount, Strin return; } - unsafe + Debug.Assert(replacements != null, "replacements was null when replacementsCount > 0"); + // calculate the total amount of extra space or space needed for all the replacements. + long longDelta = (value.Length - removeCount) * (long)replacementsCount; + int delta = (int)longDelta; + if (delta != longDelta) { - fixed (char* valuePtr = value) - { - Debug.Assert(replacements != null, "replacements was null when replacementsCount > 0"); - // calculate the total amount of extra space or space needed for all the replacements. - long longDelta = (value.Length - removeCount) * (long)replacementsCount; - int delta = (int)longDelta; - if (delta != longDelta) - { - throw new OutOfMemoryException(); - } + throw new OutOfMemoryException(); + } - StringBuilder targetChunk = sourceChunk; // the target as we copy chars down - int targetIndexInChunk = replacements[0]; + StringBuilder targetChunk = sourceChunk; // the target as we copy chars down + int targetIndexInChunk = replacements[0]; - // Make the room needed for all the new characters if needed. - if (delta > 0) - { - MakeRoom(targetChunk.m_ChunkOffset + targetIndexInChunk, delta, out targetChunk, out targetIndexInChunk, true); - } - - // We made certain that characters after the insertion point are not moved, - int i = 0; - while (true) - { - // Copy in the new string for the ith replacement - ReplaceInPlaceAtChunk(ref targetChunk!, ref targetIndexInChunk, valuePtr, value.Length); - int gapStart = replacements[i] + removeCount; - i++; - if (i >= replacementsCount) - { - break; - } + // Make the room needed for all the new characters if needed. + if (delta > 0) + { + MakeRoom(targetChunk.m_ChunkOffset + targetIndexInChunk, delta, out targetChunk, out targetIndexInChunk, true); + } - int gapEnd = replacements[i]; - Debug.Assert(gapStart < sourceChunk.m_ChunkChars.Length, "gap starts at end of buffer. Should not happen"); - Debug.Assert(gapStart <= gapEnd, "negative gap size"); - Debug.Assert(gapEnd <= sourceChunk.m_ChunkLength, "gap too big"); - if (delta != 0) // can skip the sliding of gaps if source an target string are the same size. - { - // Copy the gap data between the current replacement and the next replacement - fixed (char* sourcePtr = &sourceChunk.m_ChunkChars[gapStart]) - ReplaceInPlaceAtChunk(ref targetChunk!, ref targetIndexInChunk, sourcePtr, gapEnd - gapStart); - } - else - { - targetIndexInChunk += gapEnd - gapStart; - Debug.Assert(targetIndexInChunk <= targetChunk.m_ChunkLength, "gap not in chunk"); - } - } + // We made certain that characters after the insertion point are not moved, + int i = 0; + while (true) + { + // Copy in the new string for the ith replacement + ReplaceInPlaceAtChunk(ref targetChunk!, ref targetIndexInChunk, value); + int gapStart = replacements[i] + removeCount; + i++; + if (i >= replacementsCount) + { + break; + } - // Remove extra space if necessary. - if (delta < 0) - { - Remove(targetChunk.m_ChunkOffset + targetIndexInChunk, -delta, out targetChunk, out targetIndexInChunk); - } + int gapEnd = replacements[i]; + Debug.Assert(gapStart < sourceChunk.m_ChunkChars.Length, "gap starts at end of buffer. Should not happen"); + Debug.Assert(gapStart <= gapEnd, "negative gap size"); + Debug.Assert(gapEnd <= sourceChunk.m_ChunkLength, "gap too big"); + if (delta != 0) // can skip the sliding of gaps if source an target string are the same size. + { + // Copy the gap data between the current replacement and the next replacement + ReplaceInPlaceAtChunk(ref targetChunk!, ref targetIndexInChunk, sourceChunk.m_ChunkChars.AsSpan(gapStart, gapEnd - gapStart)); + } + else + { + targetIndexInChunk += gapEnd - gapStart; + Debug.Assert(targetIndexInChunk <= targetChunk.m_ChunkLength, "gap not in chunk"); } } + + // Remove extra space if necessary. + if (delta < 0) + { + Remove(targetChunk.m_ChunkOffset + targetIndexInChunk, -delta, out targetChunk, out targetIndexInChunk); + } } /// @@ -2287,35 +2145,26 @@ private bool StartsWith(StringBuilder chunk, int indexInChunk, int count, string /// The index in to start replacing characters at. /// Receives the index at which character replacement ends. /// - /// The pointer to the start of the character buffer. - /// The number of characters in the buffer. - private unsafe void ReplaceInPlaceAtChunk(ref StringBuilder? chunk, ref int indexInChunk, char* value, int count) + /// The character buffer. + private void ReplaceInPlaceAtChunk(ref StringBuilder? chunk, ref int indexInChunk, ReadOnlySpan value) { - if (count != 0) + while (!value.IsEmpty) { - while (true) - { - Debug.Assert(chunk != null, "chunk should not be null at this point"); - int lengthInChunk = chunk.m_ChunkLength - indexInChunk; - Debug.Assert(lengthInChunk >= 0, "Index isn't in the chunk."); + Debug.Assert(chunk != null, "chunk should not be null at this point"); + int lengthInChunk = chunk.m_ChunkLength - indexInChunk; + Debug.Assert(lengthInChunk >= 0, "Index isn't in the chunk."); - int lengthToCopy = Math.Min(lengthInChunk, count); - new ReadOnlySpan(value, lengthToCopy).CopyTo(chunk.m_ChunkChars.AsSpan(indexInChunk)); + int lengthToCopy = Math.Min(lengthInChunk, value.Length); + value.Slice(0, lengthToCopy).CopyTo(chunk.m_ChunkChars.AsSpan(indexInChunk)); - // Advance the index. - indexInChunk += lengthToCopy; - if (indexInChunk >= chunk.m_ChunkLength) - { - chunk = Next(chunk); - indexInChunk = 0; - } - count -= lengthToCopy; - if (count == 0) - { - break; - } - value += lengthToCopy; + // Advance the index. + indexInChunk += lengthToCopy; + if (indexInChunk >= chunk.m_ChunkLength) + { + chunk = Next(chunk); + indexInChunk = 0; } + value = value.Slice(lengthToCopy); } } diff --git a/src/libraries/System.Runtime/tests/System/Text/StringBuilderTests.cs b/src/libraries/System.Runtime/tests/System/Text/StringBuilderTests.cs index 89bf734808fe28..00ce83dda42d8b 100644 --- a/src/libraries/System.Runtime/tests/System/Text/StringBuilderTests.cs +++ b/src/libraries/System.Runtime/tests/System/Text/StringBuilderTests.cs @@ -14,7 +14,7 @@ namespace System.Text.Tests public partial class StringBuilderTests { private static readonly string s_chunkSplitSource = new string('a', 30); - private static readonly string s_noCapacityParamName = "valueCount"; + private static readonly string s_noCapacityParamName = "value"; private static StringBuilder StringBuilderWithMultipleChunks() => new StringBuilder(20).Append(s_chunkSplitSource); @@ -703,8 +703,8 @@ public static void Append_CharArray_Invalid() AssertExtensions.Throws("charCount", () => builder.Append(new char[5], 6, 0)); // Start index + count > value.Length AssertExtensions.Throws("charCount", () => builder.Append(new char[5], 5, 1)); // Start index + count > value.Length - AssertExtensions.Throws("valueCount", () => builder.Append(new char[] { 'a' })); // New length > builder.MaxCapacity - AssertExtensions.Throws("valueCount", () => builder.Append(new char[] { 'a' }, 0, 1)); // New length > builder.MaxCapacity + AssertExtensions.Throws(s_noCapacityParamName, () => builder.Append(new char[] { 'a' })); // New length > builder.MaxCapacity + AssertExtensions.Throws(s_noCapacityParamName, () => builder.Append(new char[] { 'a' }, 0, 1)); // New length > builder.MaxCapacity } public static IEnumerable AppendFormat_TestData()