Skip to content

Commit

Permalink
Optimize StringBuilder.Insert. (#64922)
Browse files Browse the repository at this point in the history
* Optimize StringBuilder.Insert.

The character array overload calls the ReadOnlySpan overload, and the overloads of primitives call a private helper function that takes advantage of ISpanFormattable.

* Throw OOM when StringBuilder.Insert exceeds capacity, like before.
  • Loading branch information
teo-tsirpanis authored Feb 10, 2022
1 parent cf35d12 commit 68bdfd3
Showing 1 changed file with 42 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,9 @@ public void CopyTo(int sourceIndex, Span<char> destination, int count)
/// <param name="index">The index to insert in this builder.</param>
/// <param name="value">The string to insert.</param>
/// <param name="count">The number of times to insert the string.</param>
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<char> value, int count)
{
if (count < 0)
{
Expand All @@ -979,7 +981,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;
}
Expand All @@ -997,7 +999,7 @@ public StringBuilder Insert(int index, string? value, int count)

while (count > 0)
{
ReplaceInPlaceAtChunk(ref chunk!, ref indexInChunk, ref value.GetRawStringData(), value.Length);
ReplaceInPlaceAtChunk(ref chunk!, ref indexInChunk, ref MemoryMarshal.GetReference(value), value.Length);
--count;
}

Expand Down Expand Up @@ -1275,17 +1277,25 @@ public StringBuilder Insert(int index, string? value)
return this;
}

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
// bool does not implement ISpanFormattable but its ToString override returns cached strings.
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)
{
if ((uint)index > (uint)Length)
{
throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
}

Insert(index, ref value, 1);
return this;
}
Expand All @@ -1299,7 +1309,7 @@ public StringBuilder Insert(int index, char[]? value)

if (value != null)
{
Insert(index, value, 0, value.Length);
Insert(index, ref MemoryMarshal.GetArrayDataReference(value), value.Length);
}
return this;
}
Expand Down Expand Up @@ -1344,24 +1354,24 @@ public StringBuilder Insert(int index, char[]? value, int startIndex, int charCo
return this;
}

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);

Expand All @@ -1380,6 +1390,21 @@ public StringBuilder Insert(int index, ReadOnlySpan<char> value)
return this;
}

private StringBuilder InsertSpanFormattable<T>(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<char> buffer = stackalloc char[256];
if (value.TryFormat(buffer, out int charsWritten, format: default, provider: null))
{
// We don't use Insert(int, ReadOnlySpan<char>) 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(), 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));
Expand Down Expand Up @@ -2046,10 +2071,7 @@ private void AppendWithExpansion(ref char value, int valueCount)
/// <param name="valueCount">The number of characters in the buffer.</param>
private void Insert(int index, ref char value, int valueCount)
{
if ((uint)index > (uint)Length)
{
throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
}
Debug.Assert((uint)index <= (uint)Length, "Callers should check that index is a legal value.");

if (valueCount > 0)
{
Expand Down

0 comments on commit 68bdfd3

Please sign in to comment.