Skip to content

Commit ff57624

Browse files
authored
StringBuilder: use Span.Fill in Append repeating char (#86287)
1 parent 5a03596 commit ff57624

File tree

2 files changed

+53
-23
lines changed

2 files changed

+53
-23
lines changed

src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -659,36 +659,57 @@ public StringBuilder Append(char value, int repeatCount)
659659
return this;
660660
}
661661

662-
// this is where we can check if the repeatCount will put us over m_MaxCapacity
663-
// We are doing the check here to prevent the corruption of the StringBuilder.
664-
int newLength = Length + repeatCount;
665-
if (newLength > m_MaxCapacity || newLength < repeatCount)
662+
char[] chunkChars = m_ChunkChars;
663+
int chunkLength = m_ChunkLength;
664+
665+
// Try to fit the whole repeatCount in the current chunk
666+
// Use the same check as Span<T>.Slice for 64-bit so it can be folded
667+
// Since repeatCount can't be negative, there's no risk for it to overflow on 32 bit
668+
if (((nuint)(uint)chunkLength + (nuint)(uint)repeatCount) <= (nuint)(uint)chunkChars.Length)
666669
{
667-
throw new ArgumentOutOfRangeException(nameof(repeatCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
670+
chunkChars.AsSpan(chunkLength, repeatCount).Fill(value);
671+
m_ChunkLength += repeatCount;
668672
}
669-
670-
int index = m_ChunkLength;
671-
while (repeatCount > 0)
673+
else
672674
{
673-
if (index < m_ChunkChars.Length)
674-
{
675-
m_ChunkChars[index++] = value;
676-
--repeatCount;
677-
}
678-
else
679-
{
680-
m_ChunkLength = index;
681-
ExpandByABlock(repeatCount);
682-
Debug.Assert(m_ChunkLength == 0);
683-
index = 0;
684-
}
675+
AppendWithExpansion(value, repeatCount);
685676
}
686677

687-
m_ChunkLength = index;
688678
AssertInvariants();
689679
return this;
690680
}
691681

682+
private void AppendWithExpansion(char value, int repeatCount)
683+
{
684+
Debug.Assert(repeatCount > 0, "Invalid length; should have been validated by caller.");
685+
686+
// Check if the repeatCount will put us over m_MaxCapacity
687+
if ((uint)(repeatCount + Length) > (uint)m_MaxCapacity)
688+
{
689+
throw new ArgumentOutOfRangeException(nameof(repeatCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
690+
}
691+
692+
char[] chunkChars = m_ChunkChars;
693+
int chunkLength = m_ChunkLength;
694+
695+
// Fill the rest of the current chunk
696+
int firstLength = chunkChars.Length - chunkLength;
697+
if (firstLength > 0)
698+
{
699+
chunkChars.AsSpan(chunkLength, firstLength).Fill(value);
700+
m_ChunkLength = chunkChars.Length;
701+
}
702+
703+
// Expand the builder to add another chunk
704+
int restLength = repeatCount - firstLength;
705+
ExpandByABlock(restLength);
706+
Debug.Assert(m_ChunkLength == 0, "A new block was not created.");
707+
708+
// Fill the new chunk with the remaining part of repeatCount
709+
m_ChunkChars.AsSpan(0, restLength).Fill(value);
710+
m_ChunkLength = restLength;
711+
}
712+
692713
/// <summary>
693714
/// Appends a range of characters to the end of this builder.
694715
/// </summary>
@@ -990,12 +1011,21 @@ public StringBuilder Append(char value)
9901011
}
9911012
else
9921013
{
993-
Append(value, 1);
1014+
AppendWithExpansion(value);
9941015
}
9951016

9961017
return this;
9971018
}
9981019

1020+
[MethodImpl(MethodImplOptions.NoInlining)]
1021+
private void AppendWithExpansion(char value)
1022+
{
1023+
ExpandByABlock(1);
1024+
Debug.Assert(m_ChunkLength == 0, "A new block was not created.");
1025+
m_ChunkChars[0] = value;
1026+
m_ChunkLength++;
1027+
}
1028+
9991029
[CLSCompliant(false)]
10001030
public StringBuilder Append(sbyte value) => AppendSpanFormattable(value);
10011031

src/libraries/System.Runtime/tests/System/Text/StringBuilderTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ public static void Append_Char_NoSpareCapacity_ThrowsArgumentOutOfRangeException
550550
var builder = new StringBuilder(0, 5);
551551
builder.Append("Hello");
552552

553-
AssertExtensions.Throws<ArgumentOutOfRangeException>("repeatCount", "requiredLength", () => builder.Append('a'));
553+
AssertExtensions.Throws<ArgumentOutOfRangeException>("requiredLength", () => builder.Append('a'));
554554
AssertExtensions.Throws<ArgumentOutOfRangeException>("repeatCount", "requiredLength", () => builder.Append('a', 1));
555555
}
556556

0 commit comments

Comments
 (0)