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