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 8a8d6a93b57948..a74a10af7ea8dd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs @@ -981,7 +981,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) { @@ -994,7 +996,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; } @@ -1012,7 +1014,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; } @@ -1290,17 +1292,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; } @@ -1314,7 +1324,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; } @@ -1359,24 +1369,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); @@ -1395,6 +1405,21 @@ public StringBuilder Insert(int index, ReadOnlySpan 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(), 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)); @@ -2066,10 +2091,7 @@ private void AppendWithExpansion(ref char value, int valueCount) /// The number of characters in the buffer. 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) {