f94cdbbcc5d883054cfe7273ef8977dab04e8668
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / Text / StringBuilder.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Text;
6 using System.Runtime;
7 using System.Runtime.Serialization;
8 using System;
9 using System.Runtime.CompilerServices;
10 using System.Runtime.InteropServices;
11 using System.Runtime.Versioning;
12 using System.Security;
13 using System.Threading;
14 using System.Globalization;
15 using System.Diagnostics;
16 using System.Collections.Generic;
17
18 namespace System.Text
19 {
20     // This class represents a mutable string.  It is convenient for situations in
21     // which it is desirable to modify a string, perhaps by removing, replacing, or 
22     // inserting characters, without creating a new String subsequent to
23     // each modification. 
24     // 
25     // The methods contained within this class do not return a new StringBuilder
26     // object unless specified otherwise.  This class may be used in conjunction with the String
27     // class to carry out modifications upon strings.
28     [Serializable]
29     [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
30     public sealed partial class StringBuilder : ISerializable
31     {
32         // A StringBuilder is internally represented as a linked list of blocks each of which holds
33         // a chunk of the string.  It turns out string as a whole can also be represented as just a chunk, 
34         // so that is what we do.  
35
36         /// <summary>
37         /// The character buffer for this chunk.
38         /// </summary>
39         internal char[] m_ChunkChars;
40
41         /// <summary>
42         /// The chunk that logically precedes this chunk.
43         /// </summary>
44         internal StringBuilder m_ChunkPrevious;
45
46         /// <summary>
47         /// The number of characters in this chunk.
48         /// This is the number of elements in <see cref="m_ChunkChars"/> that are in use, from the start of the buffer.
49         /// </summary>
50         internal int m_ChunkLength;
51
52         /// <summary>
53         /// The logical offset of this chunk's characters in the string it is a part of.
54         /// This is the sum of the number of characters in preceding blocks.
55         /// </summary>
56         internal int m_ChunkOffset;
57
58         /// <summary>
59         /// The maximum capacity this builder is allowed to have.
60         /// </summary>
61         internal int m_MaxCapacity;
62
63         /// <summary>
64         /// The default capacity of a <see cref="StringBuilder"/>.
65         /// </summary>
66         internal const int DefaultCapacity = 16;
67
68         private const string CapacityField = "Capacity"; // Do not rename (binary serialization)
69         private const string MaxCapacityField = "m_MaxCapacity"; // Do not rename (binary serialization)
70         private const string StringValueField = "m_StringValue"; // Do not rename (binary serialization)
71         private const string ThreadIDField = "m_currentThread"; // Do not rename (binary serialization)
72
73         // We want to keep chunk arrays out of large object heap (< 85K bytes ~ 40K chars) to be sure.
74         // Making the maximum chunk size big means less allocation code called, but also more waste
75         // in unused characters and slower inserts / replaces (since you do need to slide characters over
76         // within a buffer).  
77         internal const int MaxChunkSize = 8000;
78
79         /// <summary>
80         /// Initializes a new instance of the <see cref="StringBuilder"/> class.
81         /// </summary>
82         public StringBuilder()
83         {
84             m_MaxCapacity = int.MaxValue;
85             m_ChunkChars = new char[DefaultCapacity];
86         }
87
88         /// <summary>
89         /// Initializes a new instance of the <see cref="StringBuilder"/> class.
90         /// </summary>
91         /// <param name="capacity">The initial capacity of this builder.</param>
92         public StringBuilder(int capacity)
93             : this(capacity, int.MaxValue)
94         {
95         }
96
97         /// <summary>
98         /// Initializes a new instance of the <see cref="StringBuilder"/> class.
99         /// </summary>
100         /// <param name="value">The initial contents of this builder.</param>
101         public StringBuilder(string value)
102             : this(value, DefaultCapacity)
103         {
104         }
105
106         /// <summary>
107         /// Initializes a new instance of the <see cref="StringBuilder"/> class.
108         /// </summary>
109         /// <param name="value">The initial contents of this builder.</param>
110         /// <param name="capacity">The initial capacity of this builder.</param>
111         public StringBuilder(string value, int capacity)
112             : this(value, 0, value?.Length ?? 0, capacity)
113         {
114         }
115
116         /// <summary>
117         /// Initializes a new instance of the <see cref="StringBuilder"/> class.
118         /// </summary>
119         /// <param name="value">The initial contents of this builder.</param>
120         /// <param name="startIndex">The index to start in <paramref name="value"/>.</param>
121         /// <param name="length">The number of characters to read in <paramref name="value"/>.</param>
122         /// <param name="capacity">The initial capacity of this builder.</param>
123         public StringBuilder(string value, int startIndex, int length, int capacity)
124         {
125             if (capacity < 0)
126             {
127                 throw new ArgumentOutOfRangeException(nameof(capacity), SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(capacity)));
128             }
129             if (length < 0)
130             {
131                 throw new ArgumentOutOfRangeException(nameof(length), SR.Format(SR.ArgumentOutOfRange_MustBeNonNegNum, nameof(length)));
132             }
133             if (startIndex < 0)
134             {
135                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
136             }
137
138             if (value == null)
139             {
140                 value = string.Empty;
141             }
142             if (startIndex > value.Length - length)
143             {
144                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_IndexLength);
145             }
146
147             m_MaxCapacity = int.MaxValue;
148             if (capacity == 0)
149             {
150                 capacity = DefaultCapacity;
151             }
152             capacity = Math.Max(capacity, length);
153
154             m_ChunkChars = new char[capacity];
155             m_ChunkLength = length;
156
157             unsafe
158             {
159                 fixed (char* sourcePtr = value)
160                 {
161                     ThreadSafeCopy(sourcePtr + startIndex, m_ChunkChars, 0, length);
162                 }
163             }
164         }
165
166         /// <summary>
167         /// Initializes a new instance of the <see cref="StringBuilder"/> class.
168         /// </summary>
169         /// <param name="capacity">The initial capacity of this builder.</param>
170         /// <param name="maxCapacity">The maximum capacity of this builder.</param>
171         public StringBuilder(int capacity, int maxCapacity)
172         {
173             if (capacity > maxCapacity)
174             {
175                 throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_Capacity);
176             }
177             if (maxCapacity < 1)
178             {
179                 throw new ArgumentOutOfRangeException(nameof(maxCapacity), SR.ArgumentOutOfRange_SmallMaxCapacity);
180             }
181             if (capacity < 0)
182             {
183                 throw new ArgumentOutOfRangeException(nameof(capacity), SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(capacity)));
184             }
185
186             if (capacity == 0)
187             {
188                 capacity = Math.Min(DefaultCapacity, maxCapacity);
189             }
190
191             m_MaxCapacity = maxCapacity;
192             m_ChunkChars = new char[capacity];
193         }
194
195         private StringBuilder(SerializationInfo info, StreamingContext context)
196         {
197             if (info == null)
198             {
199                 throw new ArgumentNullException(nameof(info));
200             }
201
202             int persistedCapacity = 0;
203             string persistedString = null;
204             int persistedMaxCapacity = Int32.MaxValue;
205             bool capacityPresent = false;
206
207             // Get the data
208             SerializationInfoEnumerator enumerator = info.GetEnumerator();
209             while (enumerator.MoveNext())
210             {
211                 switch (enumerator.Name)
212                 {
213                     case MaxCapacityField:
214                         persistedMaxCapacity = info.GetInt32(MaxCapacityField);
215                         break;
216                     case StringValueField:
217                         persistedString = info.GetString(StringValueField);
218                         break;
219                     case CapacityField:
220                         persistedCapacity = info.GetInt32(CapacityField);
221                         capacityPresent = true;
222                         break;
223                     default:
224                         // Ignore other fields for forwards-compatibility.
225                         break;
226                 }
227             }
228
229             // Check values and set defaults
230             if (persistedString == null)
231             {
232                 persistedString = string.Empty;
233             }
234             if (persistedMaxCapacity < 1 || persistedString.Length > persistedMaxCapacity)
235             {
236                 throw new SerializationException(SR.Serialization_StringBuilderMaxCapacity);
237             }
238
239             if (!capacityPresent)
240             {
241                 // StringBuilder in V1.X did not persist the Capacity, so this is a valid legacy code path.
242                 persistedCapacity = Math.Min(Math.Max(DefaultCapacity, persistedString.Length), persistedMaxCapacity);
243             }
244
245             if (persistedCapacity < 0 || persistedCapacity < persistedString.Length || persistedCapacity > persistedMaxCapacity)
246             {
247                 throw new SerializationException(SR.Serialization_StringBuilderCapacity);
248             }
249
250             // Assign
251             m_MaxCapacity = persistedMaxCapacity;
252             m_ChunkChars = new char[persistedCapacity];
253             persistedString.CopyTo(0, m_ChunkChars, 0, persistedString.Length);
254             m_ChunkLength = persistedString.Length;
255             m_ChunkPrevious = null;
256             AssertInvariants();
257         }
258
259         void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
260         {
261             if (info == null)
262             {
263                 throw new ArgumentNullException(nameof(info));
264             }
265
266             AssertInvariants();
267             info.AddValue(MaxCapacityField, m_MaxCapacity);
268             info.AddValue(CapacityField, Capacity);
269             info.AddValue(StringValueField, ToString());
270             // Note: persist "m_currentThread" to be compatible with old versions
271             info.AddValue(ThreadIDField, 0);
272         }
273
274         [System.Diagnostics.Conditional("DEBUG")]
275         private void AssertInvariants()
276         {
277             Debug.Assert(m_ChunkOffset + m_ChunkChars.Length >= m_ChunkOffset, "The length of the string is greater than int.MaxValue.");
278
279             StringBuilder currentBlock = this;
280             int maxCapacity = this.m_MaxCapacity;
281             for (;;)
282             {
283                 // All blocks have the same max capacity.
284                 Debug.Assert(currentBlock.m_MaxCapacity == maxCapacity);
285                 Debug.Assert(currentBlock.m_ChunkChars != null);
286
287                 Debug.Assert(currentBlock.m_ChunkLength <= currentBlock.m_ChunkChars.Length);
288                 Debug.Assert(currentBlock.m_ChunkLength >= 0);
289                 Debug.Assert(currentBlock.m_ChunkOffset >= 0);
290
291                 StringBuilder prevBlock = currentBlock.m_ChunkPrevious;
292                 if (prevBlock == null)
293                 {
294                     Debug.Assert(currentBlock.m_ChunkOffset == 0);
295                     break;
296                 }
297                 // There are no gaps in the blocks.
298                 Debug.Assert(currentBlock.m_ChunkOffset == prevBlock.m_ChunkOffset + prevBlock.m_ChunkLength);
299                 currentBlock = prevBlock;
300             }
301         }
302
303         public int Capacity
304         {
305             get { return m_ChunkChars.Length + m_ChunkOffset; }
306             set
307             {
308                 if (value < 0)
309                 {
310                     throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NegativeCapacity);
311                 }
312                 if (value > MaxCapacity)
313                 {
314                     throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_Capacity);
315                 }
316                 if (value < Length)
317                 {
318                     throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_SmallCapacity);
319                 }
320
321                 if (Capacity != value)
322                 {
323                     int newLen = value - m_ChunkOffset;
324                     char[] newArray = new char[newLen];
325                     Array.Copy(m_ChunkChars, 0, newArray, 0, m_ChunkLength);
326                     m_ChunkChars = newArray;
327                 }
328             }
329         }
330
331         /// <summary>
332         /// Gets the maximum capacity this builder is allowed to have.
333         /// </summary>
334         public int MaxCapacity => m_MaxCapacity;
335
336         /// <summary>
337         /// Ensures that the capacity of this builder is at least the specified value.
338         /// </summary>
339         /// <param name="capacity">The new capacity for this builder.</param>
340         /// <remarks>
341         /// If <paramref name="capacity"/> is less than or equal to the current capacity of
342         /// this builder, the capacity remains unchanged.
343         /// </remarks>
344         public int EnsureCapacity(int capacity)
345         {
346             if (capacity < 0)
347             {
348                 throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NegativeCapacity);
349             }
350
351             if (Capacity < capacity)
352                 Capacity = capacity;
353             return Capacity;
354         }
355
356         public override String ToString()
357         {
358             AssertInvariants();
359
360             if (Length == 0)
361             {
362                 return string.Empty;
363             }
364
365             string result = string.FastAllocateString(Length);
366             StringBuilder chunk = this;
367             unsafe
368             {
369                 fixed (char* destinationPtr = result)
370                 {
371                     do
372                     {
373                         if (chunk.m_ChunkLength > 0)
374                         {
375                             // Copy these into local variables so that they are stable even in the presence of race conditions
376                             char[] sourceArray = chunk.m_ChunkChars;
377                             int chunkOffset = chunk.m_ChunkOffset;
378                             int chunkLength = chunk.m_ChunkLength;
379
380                             // Check that we will not overrun our boundaries. 
381                             if ((uint)(chunkLength + chunkOffset) <= (uint)result.Length && (uint)chunkLength <= (uint)sourceArray.Length)
382                             {
383                                 fixed (char* sourcePtr = &sourceArray[0])
384                                     string.wstrcpy(destinationPtr + chunkOffset, sourcePtr, chunkLength);
385                             }
386                             else
387                             {
388                                 throw new ArgumentOutOfRangeException(nameof(chunkLength), SR.ArgumentOutOfRange_Index);
389                             }
390                         }
391                         chunk = chunk.m_ChunkPrevious;
392                     }
393                     while (chunk != null);
394
395                     return result;
396                 }
397             }
398         }
399
400         /// <summary>
401         /// Creates a string from a substring of this builder.
402         /// </summary>
403         /// <param name="startIndex">The index to start in this builder.</param>
404         /// <param name="length">The number of characters to read in this builder.</param>
405         public string ToString(int startIndex, int length)
406         {
407             int currentLength = this.Length;
408             if (startIndex < 0)
409             {
410                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
411             }
412             if (startIndex > currentLength)
413             {
414                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndexLargerThanLength);
415             }
416             if (length < 0)
417             {
418                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
419             }
420             if (startIndex > currentLength - length)
421             {
422                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_IndexLength);
423             }
424
425             AssertInvariants();
426             string result = string.FastAllocateString(length);
427             unsafe
428             {
429                 fixed (char* destinationPtr = result)
430                 {
431                     this.CopyTo(startIndex, new Span<char>(destinationPtr, length), length);
432                     return result;
433                 }
434             }
435         }
436
437         public StringBuilder Clear()
438         {
439             this.Length = 0;
440             return this;
441         }
442
443         /// <summary>
444         /// Gets or sets the length of this builder.
445         /// </summary>
446         public int Length
447         {
448             get
449             {
450                 return m_ChunkOffset + m_ChunkLength;
451             }
452             set
453             {
454                 //If the new length is less than 0 or greater than our Maximum capacity, bail.
455                 if (value < 0)
456                 {
457                     throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NegativeLength);
458                 }
459
460                 if (value > MaxCapacity)
461                 {
462                     throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_SmallCapacity);
463                 }
464
465                 int originalCapacity = Capacity;
466
467                 if (value == 0 && m_ChunkPrevious == null)
468                 {
469                     m_ChunkLength = 0;
470                     m_ChunkOffset = 0;
471                     Debug.Assert(Capacity >= originalCapacity);
472                     return;
473                 }
474
475                 int delta = value - Length;
476                 if (delta > 0)
477                 {
478                     // Pad ourselves with null characters.
479                     Append('\0', delta);
480                 }
481                 else
482                 {
483                     StringBuilder chunk = FindChunkForIndex(value);
484                     if (chunk != this)
485                     {
486                         // We crossed a chunk boundary when reducing the Length. We must replace this middle-chunk with a new larger chunk,
487                         // to ensure the original capacity is preserved.
488                         int newLen = originalCapacity - chunk.m_ChunkOffset;
489                         char[] newArray = new char[newLen];
490
491                         Debug.Assert(newLen > chunk.m_ChunkChars.Length, "The new chunk should be larger than the one it is replacing.");
492                         Array.Copy(chunk.m_ChunkChars, 0, newArray, 0, chunk.m_ChunkLength);
493
494                         m_ChunkChars = newArray;
495                         m_ChunkPrevious = chunk.m_ChunkPrevious;
496                         m_ChunkOffset = chunk.m_ChunkOffset;
497                     }
498                     m_ChunkLength = value - chunk.m_ChunkOffset;
499                     AssertInvariants();
500                 }
501                 Debug.Assert(Capacity >= originalCapacity);
502             }
503         }
504
505         [IndexerName("Chars")]
506         public char this[int index]
507         {
508             get
509             {
510                 StringBuilder chunk = this;
511                 for (;;)
512                 {
513                     int indexInBlock = index - chunk.m_ChunkOffset;
514                     if (indexInBlock >= 0)
515                     {
516                         if (indexInBlock >= chunk.m_ChunkLength)
517                         {
518                             throw new IndexOutOfRangeException();
519                         }
520                         return chunk.m_ChunkChars[indexInBlock];
521                     }
522                     chunk = chunk.m_ChunkPrevious;
523                     if (chunk == null)
524                     {
525                         throw new IndexOutOfRangeException();
526                     }
527                 }
528             }
529             set
530             {
531                 StringBuilder chunk = this;
532                 for (;;)
533                 {
534                     int indexInBlock = index - chunk.m_ChunkOffset;
535                     if (indexInBlock >= 0)
536                     {
537                         if (indexInBlock >= chunk.m_ChunkLength)
538                         {
539                             throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
540                         }
541                         chunk.m_ChunkChars[indexInBlock] = value;
542                         return;
543                     }
544                     chunk = chunk.m_ChunkPrevious;
545                     if (chunk == null)
546                     {
547                         throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
548                     }
549                 }
550             }
551         }
552
553         /// <summary>
554         /// Appends a character 0 or more times to the end of this builder.
555         /// </summary>
556         /// <param name="value">The character to append.</param>
557         /// <param name="repeatCount">The number of times to append <paramref name="value"/>.</param>
558         public StringBuilder Append(char value, int repeatCount)
559         {
560             if (repeatCount < 0)
561             {
562                 throw new ArgumentOutOfRangeException(nameof(repeatCount), SR.ArgumentOutOfRange_NegativeCount);
563             }
564
565             if (repeatCount == 0)
566             {
567                 return this;
568             }
569
570             // this is where we can check if the repeatCount will put us over m_MaxCapacity
571             // We are doing the check here to prevent the corruption of the StringBuilder.
572             int newLength = Length + repeatCount;
573             if (newLength > m_MaxCapacity || newLength < repeatCount)
574             {
575                 throw new ArgumentOutOfRangeException(nameof(repeatCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
576             }
577
578             int index = m_ChunkLength;
579             while (repeatCount > 0)
580             {
581                 if (index < m_ChunkChars.Length)
582                 {
583                     m_ChunkChars[index++] = value;
584                     --repeatCount;
585                 }
586                 else
587                 {
588                     m_ChunkLength = index;
589                     ExpandByABlock(repeatCount);
590                     Debug.Assert(m_ChunkLength == 0);
591                     index = 0;
592                 }
593             }
594
595             m_ChunkLength = index;
596             AssertInvariants();
597             return this;
598         }
599
600         /// <summary>
601         /// Appends a range of characters to the end of this builder.
602         /// </summary>
603         /// <param name="value">The characters to append.</param>
604         /// <param name="startIndex">The index to start in <paramref name="value"/>.</param>
605         /// <param name="charCount">The number of characters to read in <paramref name="value"/>.</param>
606         public StringBuilder Append(char[] value, int startIndex, int charCount)
607         {
608             if (startIndex < 0)
609             {
610                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_GenericPositive);
611             }
612             if (charCount < 0)
613             {
614                 throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GenericPositive);
615             }
616
617             if (value == null)
618             {
619                 if (startIndex == 0 && charCount == 0)
620                 {
621                     return this;
622                 }
623
624                 throw new ArgumentNullException(nameof(value));
625             }
626             if (charCount > value.Length - startIndex)
627             {
628                 throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_Index);
629             }
630
631             if (charCount == 0)
632             {
633                 return this;
634             }
635
636             unsafe
637             {
638                 fixed (char* valueChars = &value[startIndex])
639                 {
640                     Append(valueChars, charCount);
641                     return this;
642                 }
643             }
644         }
645
646
647         /// <summary>
648         /// Appends a string to the end of this builder.
649         /// </summary>
650         /// <param name="value">The string to append.</param>
651         public StringBuilder Append(String value)
652         {
653             if (value != null)
654             {
655                 // We could have just called AppendHelper here; this is a hand-specialization of that code.
656                 char[] chunkChars = m_ChunkChars;
657                 int chunkLength = m_ChunkLength;
658                 int valueLen = value.Length;
659                 int newCurrentIndex = chunkLength + valueLen;
660
661                 if (newCurrentIndex < chunkChars.Length) // Use strictly < to avoid issues if count == 0, newIndex == length
662                 {
663                     if (valueLen <= 2)
664                     {
665                         if (valueLen > 0)
666                             chunkChars[chunkLength] = value[0];
667                         if (valueLen > 1)
668                             chunkChars[chunkLength + 1] = value[1];
669                     }
670                     else
671                     {
672                         unsafe
673                         {
674                             fixed (char* valuePtr = value)
675                             fixed (char* destPtr = &chunkChars[chunkLength])
676                             {
677                                 string.wstrcpy(destPtr, valuePtr, valueLen);
678                             }
679                         }
680                     }
681
682                     m_ChunkLength = newCurrentIndex;
683                 }
684                 else
685                 {
686                     AppendHelper(value);
687                 }
688             }
689
690             return this;
691         }
692
693         // We put this fixed in its own helper to avoid the cost of zero-initing `valueChars` in the
694         // case we don't actually use it.  
695         private void AppendHelper(string value)
696         {
697             unsafe
698             {
699                 fixed (char* valueChars = value)
700                 {
701                     Append(valueChars, value.Length);
702                 }
703             }
704         }
705
706         /// <summary>
707         /// Appends part of a string to the end of this builder.
708         /// </summary>
709         /// <param name="value">The string to append.</param>
710         /// <param name="startIndex">The index to start in <paramref name="value"/>.</param>
711         /// <param name="count">The number of characters to read in <paramref name="value"/>.</param>
712         public StringBuilder Append(string value, int startIndex, int count)
713         {
714             if (startIndex < 0)
715             {
716                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
717             }
718             if (count < 0)
719             {
720                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_GenericPositive);
721             }
722
723             if (value == null)
724             {
725                 if (startIndex == 0 && count == 0)
726                 {
727                     return this;
728                 }
729                 throw new ArgumentNullException(nameof(value));
730             }
731
732             if (count == 0)
733             {
734                 return this;
735             }
736
737             if (startIndex > value.Length - count)
738             {
739                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
740             }
741
742             unsafe
743             {
744                 fixed (char* valueChars = value)
745                 {
746                     Append(valueChars + startIndex, count);
747                     return this;
748                 }
749             }
750         }
751
752         public StringBuilder Append(StringBuilder value)
753         {
754             if (value != null && value.Length != 0)
755             {
756                 return AppendCore(value, 0, value.Length);
757             }
758             return this;
759         }
760
761         public StringBuilder Append(StringBuilder value, int startIndex, int count)
762         {
763             if (startIndex < 0)
764             {
765                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
766             }
767
768             if (count < 0)
769             {
770                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_GenericPositive);
771             }
772
773             if (value == null)
774             {
775                 if (startIndex == 0 && count == 0)
776                 {
777                     return this;
778                 }
779                 throw new ArgumentNullException(nameof(value));
780             }
781
782             if (count == 0)
783             {
784                 return this;
785             }
786
787             if (count > value.Length - startIndex)
788             {
789                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
790             }
791
792             return AppendCore(value, startIndex, count);
793         }
794
795         private StringBuilder AppendCore(StringBuilder value, int startIndex, int count)
796         {
797             if (value == this)
798                 return Append(value.ToString(startIndex, count));
799
800             int newLength = Length + count;
801
802             if ((uint)newLength > (uint)m_MaxCapacity)
803             {
804                 throw new ArgumentOutOfRangeException(nameof(Capacity), SR.ArgumentOutOfRange_Capacity);
805             }
806
807             while (count > 0)
808             {
809                 int length = Math.Min(m_ChunkChars.Length - m_ChunkLength, count);
810                 if (length == 0)
811                 {
812                     ExpandByABlock(count);
813                     length = Math.Min(m_ChunkChars.Length - m_ChunkLength, count);
814                 }
815                 value.CopyTo(startIndex, new Span<char>(m_ChunkChars, m_ChunkLength, length), length);
816
817                 m_ChunkLength += length;
818                 startIndex += length;
819                 count -= length;
820             }
821
822             return this;
823         }
824
825         public StringBuilder AppendLine() => Append(Environment.NewLine);
826
827         public StringBuilder AppendLine(string value)
828         {
829             Append(value);
830             return Append(Environment.NewLine);
831         }
832
833         public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
834         {
835             if (destination == null)
836             {
837                 throw new ArgumentNullException(nameof(destination));
838             }
839
840             if (destinationIndex < 0)
841             {
842                 throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.Format(SR.ArgumentOutOfRange_MustBeNonNegNum, nameof(destinationIndex)));
843             }
844
845             if (destinationIndex > destination.Length - count)
846             {
847                 throw new ArgumentException(SR.ArgumentOutOfRange_OffsetOut);
848             }
849
850             CopyTo(sourceIndex, new Span<char>(destination).Slice(destinationIndex), count);
851         }
852
853         public void CopyTo(int sourceIndex, Span<char> destination, int count)
854         {
855             if (count < 0)
856             {
857                 throw new ArgumentOutOfRangeException(nameof(count), SR.Arg_NegativeArgCount);
858             }
859
860             if ((uint)sourceIndex > (uint)Length)
861             {
862                 throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_Index);
863             }
864
865             if (sourceIndex > Length - count)
866             {
867                 throw new ArgumentException(SR.Arg_LongerThanSrcString);
868             }
869
870             AssertInvariants();
871
872             StringBuilder chunk = this;
873             int sourceEndIndex = sourceIndex + count;
874             int curDestIndex = count;
875             while (count > 0)
876             {
877                 int chunkEndIndex = sourceEndIndex - chunk.m_ChunkOffset;
878                 if (chunkEndIndex >= 0)
879                 {
880                     chunkEndIndex = Math.Min(chunkEndIndex, chunk.m_ChunkLength);
881
882                     int chunkCount = count;
883                     int chunkStartIndex = chunkEndIndex - count;
884                     if (chunkStartIndex < 0)
885                     {
886                         chunkCount += chunkStartIndex;
887                         chunkStartIndex = 0;
888                     }
889                     curDestIndex -= chunkCount;
890                     count -= chunkCount;
891
892                     // We ensure that chunkStartIndex + chunkCount are within range of m_chunkChars as well as
893                     // ensuring that curDestIndex + chunkCount are within range of destination
894                     ThreadSafeCopy(chunk.m_ChunkChars, chunkStartIndex, destination, curDestIndex, chunkCount);
895                 }
896                 chunk = chunk.m_ChunkPrevious;
897             }
898         }
899
900         /// <summary>
901         /// Inserts a string 0 or more times into this builder at the specified position.
902         /// </summary>
903         /// <param name="index">The index to insert in this builder.</param>
904         /// <param name="value">The string to insert.</param>
905         /// <param name="count">The number of times to insert the string.</param>
906         public StringBuilder Insert(int index, String value, int count)
907         {
908             if (count < 0)
909             {
910                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
911             }
912
913             int currentLength = Length;
914             if ((uint)index > (uint)currentLength)
915             {
916                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
917             }
918
919             if (string.IsNullOrEmpty(value) || count == 0)
920             {
921                 return this;
922             }
923
924             // Ensure we don't insert more chars than we can hold, and we don't 
925             // have any integer overflow in our new length.
926             long insertingChars = (long)value.Length * count;
927             if (insertingChars > MaxCapacity - this.Length)
928             {
929                 throw new OutOfMemoryException();
930             }
931             Debug.Assert(insertingChars + this.Length < int.MaxValue);
932
933             StringBuilder chunk;
934             int indexInChunk;
935             MakeRoom(index, (int)insertingChars, out chunk, out indexInChunk, false);
936             unsafe
937             {
938                 fixed (char* valuePtr = value)
939                 {
940                     while (count > 0)
941                     {
942                         ReplaceInPlaceAtChunk(ref chunk, ref indexInChunk, valuePtr, value.Length);
943                         --count;
944                     }
945
946                     return this;
947                 }
948             }
949         }
950
951         /// <summary>
952         /// Removes a range of characters from this builder.
953         /// </summary>
954         /// <remarks>
955         /// This method does not reduce the capacity of this builder.
956         /// </remarks>
957         public StringBuilder Remove(int startIndex, int length)
958         {
959             if (length < 0)
960             {
961                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
962             }
963
964             if (startIndex < 0)
965             {
966                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
967             }
968
969             if (length > Length - startIndex)
970             {
971                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Index);
972             }
973
974             if (Length == length && startIndex == 0)
975             {
976                 Length = 0;
977                 return this;
978             }
979
980             if (length > 0)
981             {
982                 StringBuilder chunk;
983                 int indexInChunk;
984                 Remove(startIndex, length, out chunk, out indexInChunk);
985             }
986
987             return this;
988         }
989
990         public StringBuilder Append(bool value) => Append(value.ToString());
991
992         public StringBuilder Append(char value)
993         {
994             if (m_ChunkLength < m_ChunkChars.Length)
995             {
996                 m_ChunkChars[m_ChunkLength++] = value;
997             }
998             else
999             {
1000                 Append(value, 1);
1001             }
1002
1003             return this;
1004         }
1005
1006         [CLSCompliant(false)]
1007         public StringBuilder Append(sbyte value) => AppendSpanFormattable(value);
1008
1009         public StringBuilder Append(byte value) => AppendSpanFormattable(value);
1010
1011         public StringBuilder Append(short value) => AppendSpanFormattable(value);
1012
1013         public StringBuilder Append(int value) => AppendSpanFormattable(value);
1014
1015         public StringBuilder Append(long value) => AppendSpanFormattable(value);
1016
1017         public StringBuilder Append(float value) => AppendSpanFormattable(value);
1018
1019         public StringBuilder Append(double value) => AppendSpanFormattable(value);
1020
1021         public StringBuilder Append(decimal value) => AppendSpanFormattable(value);
1022
1023         [CLSCompliant(false)]
1024         public StringBuilder Append(ushort value) => AppendSpanFormattable(value);
1025
1026         [CLSCompliant(false)]
1027         public StringBuilder Append(uint value) => AppendSpanFormattable(value);
1028
1029         [CLSCompliant(false)]
1030         public StringBuilder Append(ulong value) => AppendSpanFormattable(value);
1031
1032         private StringBuilder AppendSpanFormattable<T>(T value) where T : ISpanFormattable
1033         {
1034             if (value.TryFormat(RemainingCurrentChunk, out int charsWritten, format: default, provider: null))
1035             {
1036                 m_ChunkLength += charsWritten;
1037                 return this;
1038             }
1039
1040             return Append(value.ToString());
1041         }
1042
1043         public StringBuilder Append(object value) => (value == null) ? this : Append(value.ToString());
1044
1045         public StringBuilder Append(char[] value)
1046         {
1047             if (value?.Length > 0)
1048             {
1049                 unsafe
1050                 {
1051                     fixed (char* valueChars = &value[0])
1052                     {
1053                         Append(valueChars, value.Length);
1054                     }
1055                 }
1056             }
1057             return this;
1058         }
1059
1060         public StringBuilder Append(ReadOnlySpan<char> value)
1061         {
1062             if (value.Length > 0)
1063             {
1064                 unsafe
1065                 {
1066                     fixed (char* valueChars = &MemoryMarshal.GetReference(value))
1067                     {
1068                         Append(valueChars, value.Length);
1069                     }
1070                 }
1071             }
1072             return this;
1073         }
1074
1075         #region AppendJoin
1076
1077         public unsafe StringBuilder AppendJoin(string separator, params object[] values)
1078         {
1079             separator = separator ?? string.Empty;
1080             fixed (char* pSeparator = separator)
1081             {
1082                 return AppendJoinCore(pSeparator, separator.Length, values);
1083             }
1084         }
1085
1086         public unsafe StringBuilder AppendJoin<T>(string separator, IEnumerable<T> values)
1087         {
1088             separator = separator ?? string.Empty;
1089             fixed (char* pSeparator = separator)
1090             {
1091                 return AppendJoinCore(pSeparator, separator.Length, values);
1092             }
1093         }
1094
1095         public unsafe StringBuilder AppendJoin(string separator, params string[] values)
1096         {
1097             separator = separator ?? string.Empty;
1098             fixed (char* pSeparator = separator)
1099             {
1100                 return AppendJoinCore(pSeparator, separator.Length, values);
1101             }
1102         }
1103
1104         public unsafe StringBuilder AppendJoin(char separator, params object[] values)
1105         {
1106             return AppendJoinCore(&separator, 1, values);
1107         }
1108
1109         public unsafe StringBuilder AppendJoin<T>(char separator, IEnumerable<T> values)
1110         {
1111             return AppendJoinCore(&separator, 1, values);
1112         }
1113
1114         public unsafe StringBuilder AppendJoin(char separator, params string[] values)
1115         {
1116             return AppendJoinCore(&separator, 1, values);
1117         }
1118         
1119         private unsafe StringBuilder AppendJoinCore<T>(char* separator, int separatorLength, IEnumerable<T> values)
1120         {
1121             Debug.Assert(separator != null);
1122             Debug.Assert(separatorLength >= 0);
1123
1124             if (values == null)
1125             {
1126                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values);
1127             }
1128
1129             using (IEnumerator<T> en = values.GetEnumerator())
1130             {
1131                 if (!en.MoveNext())
1132                 {
1133                     return this;
1134                 }
1135
1136                 var value = en.Current;
1137                 if (value != null)
1138                 {
1139                     Append(value.ToString());
1140                 }
1141
1142                 while (en.MoveNext())
1143                 {
1144                     Append(separator, separatorLength);
1145                     value = en.Current;
1146                     if (value != null)
1147                     {
1148                         Append(value.ToString());
1149                     }
1150                 }
1151             }
1152             return this;
1153         }
1154
1155         private unsafe StringBuilder AppendJoinCore<T>(char* separator, int separatorLength, T[] values)
1156         {
1157             if (values == null)
1158             {
1159                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values);
1160             }
1161
1162             if (values.Length == 0)
1163             {
1164                 return this;
1165             }
1166
1167             if (values[0] != null)
1168             {
1169                 Append(values[0].ToString());
1170             }
1171
1172             for (int i = 1; i < values.Length; i++)
1173             {
1174                 Append(separator, separatorLength);
1175                 if (values[i] != null)
1176                 {
1177                     Append(values[i].ToString());
1178                 }
1179             }
1180             return this;
1181         }
1182
1183         #endregion
1184
1185         public StringBuilder Insert(int index, String value)
1186         {
1187             if ((uint)index > (uint)Length)
1188             {
1189                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
1190             }
1191
1192             if (value != null)
1193             {
1194                 unsafe
1195                 {
1196                     fixed (char* sourcePtr = value)
1197                         Insert(index, sourcePtr, value.Length);
1198                 }
1199             }
1200             return this;
1201         }
1202
1203         public StringBuilder Insert(int index, bool value) => Insert(index, value.ToString(), 1);
1204
1205         [CLSCompliant(false)]
1206         public StringBuilder Insert(int index, sbyte value) => Insert(index, value.ToString(), 1);
1207
1208         public StringBuilder Insert(int index, byte value) => Insert(index, value.ToString(), 1);
1209
1210         public StringBuilder Insert(int index, short value) => Insert(index, value.ToString(), 1);
1211
1212         public StringBuilder Insert(int index, char value)
1213         {
1214             unsafe
1215             {
1216                 Insert(index, &value, 1);
1217             }
1218             return this;
1219         }
1220
1221         public StringBuilder Insert(int index, char[] value)
1222         {
1223             if ((uint)index > (uint)Length)
1224             {
1225                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
1226             }
1227
1228             if (value != null)
1229                 Insert(index, value, 0, value.Length);
1230             return this;
1231         }
1232
1233         public StringBuilder Insert(int index, char[] value, int startIndex, int charCount)
1234         {
1235             int currentLength = Length;
1236             if ((uint)index > (uint)currentLength)
1237             {
1238                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
1239             }
1240
1241             if (value == null)
1242             {
1243                 if (startIndex == 0 && charCount == 0)
1244                 {
1245                     return this;
1246                 }
1247                 throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
1248             }
1249
1250             if (startIndex < 0)
1251             {
1252                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
1253             }
1254
1255             if (charCount < 0)
1256             {
1257                 throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GenericPositive);
1258             }
1259
1260             if (startIndex > value.Length - charCount)
1261             {
1262                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
1263             }
1264
1265             if (charCount > 0)
1266             {
1267                 unsafe
1268                 {
1269                     fixed (char* sourcePtr = &value[startIndex])
1270                         Insert(index, sourcePtr, charCount);
1271                 }
1272             }
1273             return this;
1274         }
1275
1276         public StringBuilder Insert(int index, int value) => Insert(index, value.ToString(), 1);
1277
1278         public StringBuilder Insert(int index, long value) => Insert(index, value.ToString(), 1);
1279
1280         public StringBuilder Insert(int index, float value) => Insert(index, value.ToString(), 1);
1281
1282         public StringBuilder Insert(int index, double value) => Insert(index, value.ToString(), 1);
1283
1284         public StringBuilder Insert(int index, decimal value) => Insert(index, value.ToString(), 1);
1285
1286         [CLSCompliant(false)]
1287         public StringBuilder Insert(int index, ushort value) => Insert(index, value.ToString(), 1);
1288
1289         [CLSCompliant(false)]
1290         public StringBuilder Insert(int index, uint value) => Insert(index, value.ToString(), 1);
1291
1292         [CLSCompliant(false)]
1293         public StringBuilder Insert(int index, ulong value) => Insert(index, value.ToString(), 1);
1294
1295         public StringBuilder Insert(int index, Object value) => (value == null) ? this : Insert(index, value.ToString(), 1);
1296
1297         public StringBuilder Insert(int index, ReadOnlySpan<char> value)
1298         {
1299             if ((uint)index > (uint)Length)
1300             {
1301                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
1302             }
1303
1304             if (value.Length > 0)
1305             {
1306                 unsafe
1307                 {
1308                     fixed (char* sourcePtr = &MemoryMarshal.GetReference(value))
1309                         Insert(index, sourcePtr, value.Length);
1310                 }
1311             }
1312             return this;
1313         }
1314
1315         public StringBuilder AppendFormat(String format, Object arg0) => AppendFormatHelper(null, format, new ParamsArray(arg0));
1316
1317         public StringBuilder AppendFormat(String format, Object arg0, Object arg1) => AppendFormatHelper(null, format, new ParamsArray(arg0, arg1));
1318
1319         public StringBuilder AppendFormat(String format, Object arg0, Object arg1, Object arg2) => AppendFormatHelper(null, format, new ParamsArray(arg0, arg1, arg2));
1320
1321         public StringBuilder AppendFormat(String format, params Object[] args)
1322         {
1323             if (args == null)
1324             {
1325                 // To preserve the original exception behavior, throw an exception about format if both
1326                 // args and format are null. The actual null check for format is in AppendFormatHelper.
1327                 string paramName = (format == null) ? nameof(format) : nameof(args);
1328                 throw new ArgumentNullException(paramName);
1329             }
1330
1331             return AppendFormatHelper(null, format, new ParamsArray(args));
1332         }
1333
1334         public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0) => AppendFormatHelper(provider, format, new ParamsArray(arg0));
1335
1336         public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0, Object arg1) => AppendFormatHelper(provider, format, new ParamsArray(arg0, arg1));
1337
1338         public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0, Object arg1, Object arg2) => AppendFormatHelper(provider, format, new ParamsArray(arg0, arg1, arg2));
1339
1340         public StringBuilder AppendFormat(IFormatProvider provider, String format, params Object[] args)
1341         {
1342             if (args == null)
1343             {
1344                 // To preserve the original exception behavior, throw an exception about format if both
1345                 // args and format are null. The actual null check for format is in AppendFormatHelper.
1346                 string paramName = (format == null) ? nameof(format) : nameof(args);
1347                 throw new ArgumentNullException(paramName);
1348             }
1349
1350             return AppendFormatHelper(provider, format, new ParamsArray(args));
1351         }
1352
1353         private static void FormatError()
1354         {
1355             throw new FormatException(SR.Format_InvalidString);
1356         }
1357
1358         // Undocumented exclusive limits on the range for Argument Hole Index and Argument Hole Alignment.
1359         private const int IndexLimit = 1000000; // Note:            0 <= ArgIndex < IndexLimit
1360         private const int WidthLimit = 1000000; // Note:  -WidthLimit <  ArgAlign < WidthLimit
1361
1362         internal StringBuilder AppendFormatHelper(IFormatProvider provider, String format, ParamsArray args)
1363         {
1364             if (format == null)
1365             {
1366                 throw new ArgumentNullException(nameof(format));
1367             }
1368
1369             int pos = 0;
1370             int len = format.Length;
1371             char ch = '\x0';
1372             StringBuilder unescapedItemFormat = null;
1373
1374             ICustomFormatter cf = null;
1375             if (provider != null)
1376             {
1377                 cf = (ICustomFormatter)provider.GetFormat(typeof(ICustomFormatter));
1378             }
1379
1380             while (true)
1381             {
1382                 while (pos < len)
1383                 {
1384                     ch = format[pos];
1385
1386                     pos++;
1387                     // Is it a closing brace?
1388                     if (ch == '}')
1389                     {
1390                         // Check next character (if there is one) to see if it is escaped. eg }}
1391                         if (pos < len && format[pos] == '}')
1392                             pos++;
1393                         else
1394                             // Otherwise treat it as an error (Mismatched closing brace)
1395                             FormatError();
1396                     }
1397                     // Is it a opening brace?
1398                     if (ch == '{')
1399                     {
1400                         // Check next character (if there is one) to see if it is escaped. eg {{
1401                         if (pos < len && format[pos] == '{')
1402                             pos++;
1403                         else
1404                         {
1405                             // Otherwise treat it as the opening brace of an Argument Hole.
1406                             pos--;
1407                             break;
1408                         }
1409                     }
1410                     // If it neither then treat the character as just text.
1411                     Append(ch);
1412                 }
1413
1414                 //
1415                 // Start of parsing of Argument Hole.
1416                 // Argument Hole ::= { Index (, WS* Alignment WS*)? (: Formatting)? }
1417                 //
1418                 if (pos == len) break;
1419
1420                 //
1421                 //  Start of parsing required Index parameter.
1422                 //  Index ::= ('0'-'9')+ WS*
1423                 //
1424                 pos++;
1425                 // If reached end of text then error (Unexpected end of text)
1426                 // or character is not a digit then error (Unexpected Character)
1427                 if (pos == len || (ch = format[pos]) < '0' || ch > '9') FormatError();
1428                 int index = 0;
1429                 do
1430                 {
1431                     index = index * 10 + ch - '0';
1432                     pos++;
1433                     // If reached end of text then error (Unexpected end of text)
1434                     if (pos == len) FormatError();
1435                     ch = format[pos];
1436                     // so long as character is digit and value of the index is less than 1000000 ( index limit )
1437                 }
1438                 while (ch >= '0' && ch <= '9' && index < IndexLimit);
1439
1440                 // If value of index is not within the range of the arguments passed in then error (Index out of range)
1441                 if (index >= args.Length) throw new FormatException(SR.Format_IndexOutOfRange);
1442
1443                 // Consume optional whitespace.
1444                 while (pos < len && (ch = format[pos]) == ' ') pos++;
1445                 // End of parsing index parameter.
1446
1447                 //
1448                 //  Start of parsing of optional Alignment
1449                 //  Alignment ::= comma WS* minus? ('0'-'9')+ WS*
1450                 //
1451                 bool leftJustify = false;
1452                 int width = 0;
1453                 // Is the character a comma, which indicates the start of alignment parameter.
1454                 if (ch == ',')
1455                 {
1456                     pos++;
1457
1458                     // Consume Optional whitespace
1459                     while (pos < len && format[pos] == ' ') pos++;
1460
1461                     // If reached the end of the text then error (Unexpected end of text)
1462                     if (pos == len) FormatError();
1463
1464                     // Is there a minus sign?
1465                     ch = format[pos];
1466                     if (ch == '-')
1467                     {
1468                         // Yes, then alignment is left justified.
1469                         leftJustify = true;
1470                         pos++;
1471                         // If reached end of text then error (Unexpected end of text)
1472                         if (pos == len) FormatError();
1473                         ch = format[pos];
1474                     }
1475
1476                     // If current character is not a digit then error (Unexpected character)
1477                     if (ch < '0' || ch > '9') FormatError();
1478                     // Parse alignment digits.
1479                     do
1480                     {
1481                         width = width * 10 + ch - '0';
1482                         pos++;
1483                         // If reached end of text then error. (Unexpected end of text)
1484                         if (pos == len) FormatError();
1485                         ch = format[pos];
1486                         // So long a current character is a digit and the value of width is less than 100000 ( width limit )
1487                     }
1488                     while (ch >= '0' && ch <= '9' && width < WidthLimit);
1489                     // end of parsing Argument Alignment
1490                 }
1491
1492                 // Consume optional whitespace
1493                 while (pos < len && (ch = format[pos]) == ' ') pos++;
1494
1495                 //
1496                 // Start of parsing of optional formatting parameter.
1497                 //
1498                 Object arg = args[index];
1499                 String itemFormat = null;
1500                 ReadOnlySpan<char> itemFormatSpan = default; // used if itemFormat is null
1501                 // Is current character a colon? which indicates start of formatting parameter.
1502                 if (ch == ':')
1503                 {
1504                     pos++;
1505                     int startPos = pos;
1506
1507                     while (true)
1508                     {
1509                         // If reached end of text then error. (Unexpected end of text)
1510                         if (pos == len) FormatError();
1511                         ch = format[pos];
1512                         pos++;
1513
1514                         // Is character a opening or closing brace?
1515                         if (ch == '}' || ch == '{')
1516                         {
1517                             if (ch == '{')
1518                             {
1519                                 // Yes, is next character also a opening brace, then treat as escaped. eg {{
1520                                 if (pos < len && format[pos] == '{')
1521                                     pos++;
1522                                 else
1523                                     // Error Argument Holes can not be nested.
1524                                     FormatError();
1525                             }
1526                             else
1527                             {
1528                                 // Yes, is next character also a closing brace, then treat as escaped. eg }}
1529                                 if (pos < len && format[pos] == '}')
1530                                     pos++;
1531                                 else
1532                                 {
1533                                     // No, then treat it as the closing brace of an Arg Hole.
1534                                     pos--;
1535                                     break;
1536                                 }
1537                             }
1538
1539                             // Reaching here means the brace has been escaped
1540                             // so we need to build up the format string in segments
1541                             if (unescapedItemFormat == null)
1542                             {
1543                                 unescapedItemFormat = new StringBuilder();
1544                             }
1545                             unescapedItemFormat.Append(format, startPos, pos - startPos - 1);
1546                             startPos = pos;
1547                         }
1548                     }
1549
1550                     if (unescapedItemFormat == null || unescapedItemFormat.Length == 0)
1551                     {
1552                         if (startPos != pos)
1553                         {
1554                             // There was no brace escaping, extract the item format as a single string
1555                             itemFormatSpan = format.AsSpan().Slice(startPos, pos - startPos);
1556                         }
1557                     }
1558                     else
1559                     {
1560                         unescapedItemFormat.Append(format, startPos, pos - startPos);
1561                         itemFormatSpan = itemFormat = unescapedItemFormat.ToString();
1562                         unescapedItemFormat.Clear();
1563                     }
1564                 }
1565                 // If current character is not a closing brace then error. (Unexpected Character)
1566                 if (ch != '}') FormatError();
1567                 // Construct the output for this arg hole.
1568                 pos++;
1569                 String s = null;
1570                 if (cf != null)
1571                 {
1572                     if (itemFormatSpan.Length != 0 && itemFormat == null)
1573                     {
1574                         itemFormat = new string(itemFormatSpan);
1575                     }
1576                     s = cf.Format(itemFormat, arg, provider);
1577                 }
1578
1579                 if (s == null)
1580                 {
1581                     // If arg is ISpanFormattable and the beginning doesn't need padding,
1582                     // try formatting it into the remaining current chunk.
1583                     if (arg is ISpanFormattable spanFormattableArg &&
1584                         (leftJustify || width == 0) &&
1585                         spanFormattableArg.TryFormat(RemainingCurrentChunk, out int charsWritten, itemFormatSpan, provider))
1586                     {
1587                         m_ChunkLength += charsWritten;
1588
1589                         // Pad the end, if needed.
1590                         int padding = width - charsWritten;
1591                         if (leftJustify && padding > 0) Append(' ', padding);
1592
1593                         // Continue to parse other characters.
1594                         continue;
1595                     }
1596
1597                     // Otherwise, fallback to trying IFormattable or calling ToString.
1598                     if (arg is IFormattable formattableArg)
1599                     {
1600                         if (itemFormatSpan.Length != 0 && itemFormat == null)
1601                         {
1602                             itemFormat = new string(itemFormatSpan);
1603                         }
1604                         s = formattableArg.ToString(itemFormat, provider);
1605                     }
1606                     else if (arg != null)
1607                     {
1608                         s = arg.ToString();
1609                     }
1610                 }
1611                 // Append it to the final output of the Format String.
1612                 if (s == null) s = String.Empty;
1613                 int pad = width - s.Length;
1614                 if (!leftJustify && pad > 0) Append(' ', pad);
1615                 Append(s);
1616                 if (leftJustify && pad > 0) Append(' ', pad);
1617                 // Continue to parse other characters.
1618             }
1619             return this;
1620         }
1621
1622         /// <summary>
1623         /// Replaces all instances of one string with another in this builder.
1624         /// </summary>
1625         /// <param name="oldValue">The string to replace.</param>
1626         /// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
1627         /// <remarks>
1628         /// If <paramref name="newValue"/> is <c>null</c>, instances of <paramref name="oldValue"/>
1629         /// are removed from this builder.
1630         /// </remarks>
1631         public StringBuilder Replace(String oldValue, String newValue) => Replace(oldValue, newValue, 0, Length);
1632
1633         /// <summary>
1634         /// Determines if the contents of this builder are equal to the contents of another builder.
1635         /// </summary>
1636         /// <param name="sb">The other builder.</param>
1637         public bool Equals(StringBuilder sb)
1638         {
1639             if (sb == null)
1640                 return false;
1641             if (Capacity != sb.Capacity || MaxCapacity != sb.MaxCapacity || Length != sb.Length)
1642                 return false;
1643             if (sb == this)
1644                 return true;
1645
1646             StringBuilder thisChunk = this;
1647             int thisChunkIndex = thisChunk.m_ChunkLength;
1648             StringBuilder sbChunk = sb;
1649             int sbChunkIndex = sbChunk.m_ChunkLength;
1650             for (;;)
1651             {
1652                 --thisChunkIndex;
1653                 --sbChunkIndex;
1654
1655                 while (thisChunkIndex < 0)
1656                 {
1657                     thisChunk = thisChunk.m_ChunkPrevious;
1658                     if (thisChunk == null)
1659                         break;
1660                     thisChunkIndex = thisChunk.m_ChunkLength + thisChunkIndex;
1661                 }
1662
1663                 while (sbChunkIndex < 0)
1664                 {
1665                     sbChunk = sbChunk.m_ChunkPrevious;
1666                     if (sbChunk == null)
1667                         break;
1668                     sbChunkIndex = sbChunk.m_ChunkLength + sbChunkIndex;
1669                 }
1670
1671                 if (thisChunkIndex < 0)
1672                     return sbChunkIndex < 0;
1673                 if (sbChunkIndex < 0)
1674                     return false;
1675                 if (thisChunk.m_ChunkChars[thisChunkIndex] != sbChunk.m_ChunkChars[sbChunkIndex])
1676                     return false;
1677             }
1678         }
1679
1680         /// <summary>
1681         /// Determines if the contents of this builder are equal to the contents of ReadOnlySpan<char>.
1682         /// </summary>
1683         /// <param name="value">The ReadOnlySpan{char}.</param>
1684         public bool Equals(ReadOnlySpan<char> value)
1685         {
1686             if (value.Length != Length)
1687                 return false;
1688
1689             StringBuilder sbChunk = this;
1690             int offset = 0;
1691
1692             do
1693             {
1694                 int chunk_length = sbChunk.m_ChunkLength;
1695                 offset += chunk_length;
1696
1697                 ReadOnlySpan<char> chunk = new ReadOnlySpan<char>(sbChunk.m_ChunkChars, 0, chunk_length);
1698
1699                 if (!chunk.EqualsOrdinal(value.Slice(value.Length - offset, chunk_length)))
1700                     return false;
1701
1702                 sbChunk = sbChunk.m_ChunkPrevious;
1703             } while (sbChunk != null);
1704
1705             Debug.Assert(offset == Length);
1706             return true;
1707         }
1708
1709         /// <summary>
1710         /// Replaces all instances of one string with another in part of this builder.
1711         /// </summary>
1712         /// <param name="oldValue">The string to replace.</param>
1713         /// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
1714         /// <param name="startIndex">The index to start in this builder.</param>
1715         /// <param name="count">The number of characters to read in this builder.</param>
1716         /// <remarks>
1717         /// If <paramref name="newValue"/> is <c>null</c>, instances of <paramref name="oldValue"/>
1718         /// are removed from this builder.
1719         /// </remarks>
1720         public StringBuilder Replace(String oldValue, String newValue, int startIndex, int count)
1721         {
1722             int currentLength = Length;
1723             if ((uint)startIndex > (uint)currentLength)
1724             {
1725                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
1726             }
1727             if (count < 0 || startIndex > currentLength - count)
1728             {
1729                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Index);
1730             }
1731             if (oldValue == null)
1732             {
1733                 throw new ArgumentNullException(nameof(oldValue));
1734             }
1735             if (oldValue.Length == 0)
1736             {
1737                 throw new ArgumentException(SR.Argument_EmptyName, nameof(oldValue));
1738             }
1739
1740             newValue = newValue ?? string.Empty;
1741
1742             int deltaLength = newValue.Length - oldValue.Length;
1743
1744             int[] replacements = null; // A list of replacement positions in a chunk to apply
1745             int replacementsCount = 0;
1746
1747             // Find the chunk, indexInChunk for the starting point
1748             StringBuilder chunk = FindChunkForIndex(startIndex);
1749             int indexInChunk = startIndex - chunk.m_ChunkOffset;
1750             while (count > 0)
1751             {
1752                 // Look for a match in the chunk,indexInChunk pointer 
1753                 if (StartsWith(chunk, indexInChunk, count, oldValue))
1754                 {
1755                     // Push it on the replacements array (with growth), we will do all replacements in a
1756                     // given chunk in one operation below (see ReplaceAllInChunk) so we don't have to slide
1757                     // many times.
1758                     if (replacements == null)
1759                     {
1760                         replacements = new int[5];
1761                     }
1762                     else if (replacementsCount >= replacements.Length)
1763                     {
1764                         Array.Resize(ref replacements, replacements.Length * 3 / 2 + 4); // Grow by ~1.5x, but more in the begining
1765                     }
1766                     replacements[replacementsCount++] = indexInChunk;
1767                     indexInChunk += oldValue.Length;
1768                     count -= oldValue.Length;
1769                 }
1770                 else
1771                 {
1772                     indexInChunk++;
1773                     --count;
1774                 }
1775
1776                 if (indexInChunk >= chunk.m_ChunkLength || count == 0) // Have we moved out of the current chunk?
1777                 {
1778                     // Replacing mutates the blocks, so we need to convert to a logical index and back afterwards. 
1779                     int index = indexInChunk + chunk.m_ChunkOffset;
1780                     int indexBeforeAdjustment = index;
1781
1782                     // See if we accumulated any replacements, if so apply them.
1783                     ReplaceAllInChunk(replacements, replacementsCount, chunk, oldValue.Length, newValue);
1784                     // The replacement has affected the logical index.  Adjust it.  
1785                     index += ((newValue.Length - oldValue.Length) * replacementsCount);
1786                     replacementsCount = 0;
1787
1788                     chunk = FindChunkForIndex(index);
1789                     indexInChunk = index - chunk.m_ChunkOffset;
1790                     Debug.Assert(chunk != null || count == 0, "Chunks ended prematurely!");
1791                 }
1792             }
1793
1794             AssertInvariants();
1795             return this;
1796         }
1797
1798         /// <summary>
1799         /// Replaces all instances of one character with another in this builder.
1800         /// </summary>
1801         /// <param name="oldChar">The character to replace.</param>
1802         /// <param name="newChar">The character to replace <paramref name="oldChar"/> with.</param>
1803         public StringBuilder Replace(char oldChar, char newChar)
1804         {
1805             return Replace(oldChar, newChar, 0, Length);
1806         }
1807
1808         /// <summary>
1809         /// Replaces all instances of one character with another in this builder.
1810         /// </summary>
1811         /// <param name="oldChar">The character to replace.</param>
1812         /// <param name="newChar">The character to replace <paramref name="oldChar"/> with.</param>
1813         /// <param name="startIndex">The index to start in this builder.</param>
1814         /// <param name="count">The number of characters to read in this builder.</param>
1815         public StringBuilder Replace(char oldChar, char newChar, int startIndex, int count)
1816         {
1817             int currentLength = Length;
1818             if ((uint)startIndex > (uint)currentLength)
1819             {
1820                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
1821             }
1822
1823             if (count < 0 || startIndex > currentLength - count)
1824             {
1825                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Index);
1826             }
1827
1828             int endIndex = startIndex + count;
1829             StringBuilder chunk = this;
1830
1831             for (;;)
1832             {
1833                 int endIndexInChunk = endIndex - chunk.m_ChunkOffset;
1834                 int startIndexInChunk = startIndex - chunk.m_ChunkOffset;
1835                 if (endIndexInChunk >= 0)
1836                 {
1837                     int curInChunk = Math.Max(startIndexInChunk, 0);
1838                     int endInChunk = Math.Min(chunk.m_ChunkLength, endIndexInChunk);
1839                     while (curInChunk < endInChunk)
1840                     {
1841                         if (chunk.m_ChunkChars[curInChunk] == oldChar)
1842                             chunk.m_ChunkChars[curInChunk] = newChar;
1843                         curInChunk++;
1844                     }
1845                 }
1846                 if (startIndexInChunk >= 0)
1847                     break;
1848                 chunk = chunk.m_ChunkPrevious;
1849             }
1850
1851             AssertInvariants();
1852             return this;
1853         }
1854
1855         /// <summary>
1856         /// Appends a character buffer to this builder.
1857         /// </summary>
1858         /// <param name="value">The pointer to the start of the buffer.</param>
1859         /// <param name="valueCount">The number of characters in the buffer.</param>
1860         [CLSCompliant(false)]
1861         public unsafe StringBuilder Append(char* value, int valueCount)
1862         {
1863             // We don't check null value as this case will throw null reference exception anyway
1864             if (valueCount < 0)
1865             {
1866                 throw new ArgumentOutOfRangeException(nameof(valueCount), SR.ArgumentOutOfRange_NegativeCount);
1867             }
1868
1869             // this is where we can check if the valueCount will put us over m_MaxCapacity
1870             // We are doing the check here to prevent the corruption of the StringBuilder.
1871             int newLength = Length + valueCount;
1872             if (newLength > m_MaxCapacity || newLength < valueCount)
1873             {
1874                 throw new ArgumentOutOfRangeException(nameof(valueCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
1875             }
1876
1877             // This case is so common we want to optimize for it heavily. 
1878             int newIndex = valueCount + m_ChunkLength;
1879             if (newIndex <= m_ChunkChars.Length)
1880             {
1881                 ThreadSafeCopy(value, m_ChunkChars, m_ChunkLength, valueCount);
1882                 m_ChunkLength = newIndex;
1883             }
1884             else
1885             {
1886                 // Copy the first chunk
1887                 int firstLength = m_ChunkChars.Length - m_ChunkLength;
1888                 if (firstLength > 0)
1889                 {
1890                     ThreadSafeCopy(value, m_ChunkChars, m_ChunkLength, firstLength);
1891                     m_ChunkLength = m_ChunkChars.Length;
1892                 }
1893
1894                 // Expand the builder to add another chunk. 
1895                 int restLength = valueCount - firstLength;
1896                 ExpandByABlock(restLength);
1897                 Debug.Assert(m_ChunkLength == 0, "A new block was not created.");
1898
1899                 // Copy the second chunk
1900                 ThreadSafeCopy(value + firstLength, m_ChunkChars, 0, restLength);
1901                 m_ChunkLength = restLength;
1902             }
1903             AssertInvariants();
1904             return this;
1905         }
1906
1907         /// <summary>
1908         /// Inserts a character buffer into this builder at the specified position.
1909         /// </summary>
1910         /// <param name="index">The index to insert in this builder.</param>
1911         /// <param name="value">The pointer to the start of the buffer.</param>
1912         /// <param name="valueCount">The number of characters in the buffer.</param>
1913         private unsafe void Insert(int index, char* value, int valueCount)
1914         {
1915             if ((uint)index > (uint)Length)
1916             {
1917                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
1918             }
1919
1920             if (valueCount > 0)
1921             {
1922                 StringBuilder chunk;
1923                 int indexInChunk;
1924                 MakeRoom(index, valueCount, out chunk, out indexInChunk, false);
1925                 ReplaceInPlaceAtChunk(ref chunk, ref indexInChunk, value, valueCount);
1926             }
1927         }
1928
1929         /// <summary>
1930         /// Replaces strings at specified indices with a new string in a chunk.
1931         /// </summary>
1932         /// <param name="replacements">The list of indices, relative to the beginning of the chunk, to remove at.</param>
1933         /// <param name="replacementsCount">The number of replacements to make.</param>
1934         /// <param name="sourceChunk">The source chunk.</param>
1935         /// <param name="removeCount">The number of characters to remove at each replacement.</param>
1936         /// <param name="value">The string to insert at each replacement.</param>
1937         /// <remarks>
1938         /// This routine is very efficient because it does replacements in bulk.
1939         /// </remarks>
1940         private void ReplaceAllInChunk(int[] replacements, int replacementsCount, StringBuilder sourceChunk, int removeCount, string value)
1941         {
1942             if (replacementsCount <= 0)
1943             {
1944                 return;
1945             }
1946
1947             unsafe
1948             {
1949                 fixed (char* valuePtr = value)
1950                 {
1951                     // calculate the total amount of extra space or space needed for all the replacements.  
1952                     int delta = (value.Length - removeCount) * replacementsCount;
1953
1954                     StringBuilder targetChunk = sourceChunk;        // the target as we copy chars down
1955                     int targetIndexInChunk = replacements[0];
1956
1957                     // Make the room needed for all the new characters if needed. 
1958                     if (delta > 0)
1959                         MakeRoom(targetChunk.m_ChunkOffset + targetIndexInChunk, delta, out targetChunk, out targetIndexInChunk, true);
1960                     // We made certain that characters after the insertion point are not moved, 
1961                     int i = 0;
1962                     for (;;)
1963                     {
1964                         // Copy in the new string for the ith replacement
1965                         ReplaceInPlaceAtChunk(ref targetChunk, ref targetIndexInChunk, valuePtr, value.Length);
1966                         int gapStart = replacements[i] + removeCount;
1967                         i++;
1968                         if (i >= replacementsCount)
1969                         {
1970                             break;
1971                         }
1972
1973                         int gapEnd = replacements[i];
1974                         Debug.Assert(gapStart < sourceChunk.m_ChunkChars.Length, "gap starts at end of buffer.  Should not happen");
1975                         Debug.Assert(gapStart <= gapEnd, "negative gap size");
1976                         Debug.Assert(gapEnd <= sourceChunk.m_ChunkLength, "gap too big");
1977                         if (delta != 0)     // can skip the sliding of gaps if source an target string are the same size.  
1978                         {
1979                             // Copy the gap data between the current replacement and the next replacement
1980                             fixed (char* sourcePtr = &sourceChunk.m_ChunkChars[gapStart])
1981                                 ReplaceInPlaceAtChunk(ref targetChunk, ref targetIndexInChunk, sourcePtr, gapEnd - gapStart);
1982                         }
1983                         else
1984                         {
1985                             targetIndexInChunk += gapEnd - gapStart;
1986                             Debug.Assert(targetIndexInChunk <= targetChunk.m_ChunkLength, "gap not in chunk");
1987                         }
1988                     }
1989
1990                     // Remove extra space if necessary. 
1991                     if (delta < 0)
1992                         Remove(targetChunk.m_ChunkOffset + targetIndexInChunk, -delta, out targetChunk, out targetIndexInChunk);
1993                 }
1994             }
1995         }
1996
1997         /// <summary>
1998         /// Returns a value indicating whether a substring of a builder starts with a specified prefix.
1999         /// </summary>
2000         /// <param name="chunk">The chunk in which the substring starts.</param>
2001         /// <param name="indexInChunk">The index in <paramref name="chunk"/> at which the substring starts.</param>
2002         /// <param name="count">The logical count of the substring.</param>
2003         /// <param name="value">The prefix.</param>
2004         private bool StartsWith(StringBuilder chunk, int indexInChunk, int count, string value)
2005         {
2006             for (int i = 0; i < value.Length; i++)
2007             {
2008                 if (count == 0)
2009                 {
2010                     return false;
2011                 }
2012
2013                 if (indexInChunk >= chunk.m_ChunkLength)
2014                 {
2015                     chunk = Next(chunk);
2016                     if (chunk == null)
2017                         return false;
2018                     indexInChunk = 0;
2019                 }
2020
2021                 if (value[i] != chunk.m_ChunkChars[indexInChunk])
2022                 {
2023                     return false;
2024                 }
2025
2026                 indexInChunk++;
2027                 --count;
2028             }
2029
2030             return true;
2031         }
2032
2033         /// <summary>
2034         /// Replaces characters at a specified location with the contents of a character buffer.
2035         /// This function is the logical equivalent of memcpy.
2036         /// </summary>
2037         /// <param name="chunk">
2038         /// The chunk in which to start replacing characters.
2039         /// Receives the chunk in which character replacement ends.
2040         /// </param>
2041         /// <param name="indexInChunk">
2042         /// The index in <paramref name="chunk"/> to start replacing characters at.
2043         /// Receives the index at which character replacement ends.
2044         /// </param>
2045         /// <param name="value">The pointer to the start of the character buffer.</param>
2046         /// <param name="count">The number of characters in the buffer.</param>
2047         private unsafe void ReplaceInPlaceAtChunk(ref StringBuilder chunk, ref int indexInChunk, char* value, int count)
2048         {
2049             if (count != 0)
2050             {
2051                 for (;;)
2052                 {
2053                     int lengthInChunk = chunk.m_ChunkLength - indexInChunk;
2054                     Debug.Assert(lengthInChunk >= 0, "Index isn't in the chunk.");
2055
2056                     int lengthToCopy = Math.Min(lengthInChunk, count);
2057                     ThreadSafeCopy(value, chunk.m_ChunkChars, indexInChunk, lengthToCopy);
2058
2059                     // Advance the index. 
2060                     indexInChunk += lengthToCopy;
2061                     if (indexInChunk >= chunk.m_ChunkLength)
2062                     {
2063                         chunk = Next(chunk);
2064                         indexInChunk = 0;
2065                     }
2066                     count -= lengthToCopy;
2067                     if (count == 0)
2068                     {
2069                         break;
2070                     }
2071                     value += lengthToCopy;
2072                 }
2073             }
2074         }
2075
2076         /// <remarks>
2077         /// This method prevents out-of-bounds writes in the case a different thread updates a field in the builder just before a copy begins.
2078         /// All interesting variables are copied out of the heap into the parameters of this method, and then bounds checks are run.
2079         /// </remarks>
2080         private static unsafe void ThreadSafeCopy(char* sourcePtr, char[] destination, int destinationIndex, int count)
2081         {
2082             if (count > 0)
2083             {
2084                 if ((uint)destinationIndex <= (uint)destination.Length && (destinationIndex + count) <= destination.Length)
2085                 {
2086                     fixed (char* destinationPtr = &destination[destinationIndex])
2087                         string.wstrcpy(destinationPtr, sourcePtr, count);
2088                 }
2089                 else
2090                 {
2091                     throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.ArgumentOutOfRange_Index);
2092                 }
2093             }
2094         }
2095
2096         private static unsafe void ThreadSafeCopy(char[] source, int sourceIndex, Span<char> destination, int destinationIndex, int count)
2097         {
2098             if (count > 0)
2099             {
2100                 if ((uint)sourceIndex > (uint)source.Length || count > source.Length - sourceIndex)
2101                 {
2102                     throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_Index);
2103                 }
2104
2105                 if ((uint)destinationIndex > (uint)destination.Length || count > destination.Length - destinationIndex)
2106                 {
2107                     throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.ArgumentOutOfRange_Index);
2108                 }
2109
2110                 fixed (char* sourcePtr = &source[sourceIndex])
2111                     fixed (char* destinationPtr = &MemoryMarshal.GetReference(destination))
2112                         string.wstrcpy(destinationPtr + destinationIndex, sourcePtr, count);
2113             }
2114         }
2115
2116         /// <summary>
2117         /// Gets the chunk corresponding to the logical index in this builder.
2118         /// </summary>
2119         /// <param name="index">The logical index in this builder.</param>
2120         /// <remarks>
2121         /// After calling this method, you can obtain the actual index within the chunk by
2122         /// subtracting <see cref="m_ChunkOffset"/> from <paramref name="index"/>.
2123         /// </remarks>
2124         private StringBuilder FindChunkForIndex(int index)
2125         {
2126             Debug.Assert(0 <= index && index <= Length);
2127
2128             StringBuilder result = this;
2129             while (result.m_ChunkOffset > index)
2130             {
2131                 result = result.m_ChunkPrevious;
2132             }
2133
2134             Debug.Assert(result != null);
2135             return result;
2136         }
2137
2138         /// <summary>
2139         /// Gets the chunk corresponding to the logical byte index in this builder.
2140         /// </summary>
2141         /// <param name="byteIndex">The logical byte index in this builder.</param>
2142         private StringBuilder FindChunkForByte(int byteIndex)
2143         {
2144             Debug.Assert(0 <= byteIndex && byteIndex <= Length * sizeof(char));
2145
2146             StringBuilder result = this;
2147             while (result.m_ChunkOffset * sizeof(char) > byteIndex)
2148             {
2149                 result = result.m_ChunkPrevious;
2150             }
2151
2152             Debug.Assert(result != null);
2153             return result;
2154         }
2155
2156         /// <summary>Gets a span representing the remaining space available in the current chunk.</summary>
2157         private Span<char> RemainingCurrentChunk
2158         {
2159             [MethodImpl(MethodImplOptions.AggressiveInlining)]
2160             get => new Span<char>(m_ChunkChars, m_ChunkLength, m_ChunkChars.Length - m_ChunkLength);
2161         }
2162
2163         /// <summary>
2164         /// Finds the chunk that logically succeeds the specified chunk.
2165         /// </summary>
2166         /// <param name="chunk">The chunk whose successor should be found.</param>
2167         /// <remarks>
2168         /// Each chunk only stores the pointer to its logical predecessor, so this routine has to start
2169         /// from the 'this' pointer (which is assumed to represent the whole StringBuilder) and work its
2170         /// way down until it finds the specified chunk (which is O(n)). Thus, it is more expensive than
2171         /// a field fetch.
2172         /// </remarks>
2173         private StringBuilder Next(StringBuilder chunk) => chunk == this ? null : FindChunkForIndex(chunk.m_ChunkOffset + chunk.m_ChunkLength);
2174
2175         /// <summary>
2176         /// Transfers the character buffer from this chunk to a new chunk, and allocates a new buffer with a minimum size for this chunk.
2177         /// </summary>
2178         /// <param name="minBlockCharCount">The minimum size of the new buffer to be allocated for this chunk.</param>
2179         /// <remarks>
2180         /// This method requires that the current chunk is full. Otherwise, there's no point in shifting the characters over.
2181         /// It also assumes that 'this' is the last chunk in the linked list.
2182         /// </remarks>
2183         private void ExpandByABlock(int minBlockCharCount)
2184         {
2185             Debug.Assert(Capacity == Length, nameof(ExpandByABlock) + " should only be called when there is no space left.");
2186             Debug.Assert(minBlockCharCount > 0);
2187
2188             AssertInvariants();
2189
2190             if ((minBlockCharCount + Length) > m_MaxCapacity || minBlockCharCount + Length < minBlockCharCount)
2191             {
2192                 throw new ArgumentOutOfRangeException("requiredLength", SR.ArgumentOutOfRange_SmallCapacity);
2193             }
2194
2195             // - We always need to make the new chunk at least as big as was requested (`minBlockCharCount`).
2196             // - We'd also prefer to make it at least at big as the current length (thus doubling capacity).
2197             //   - But this is only up to a maximum, so we stay in the small object heap, and never allocate
2198             //     really big chunks even if the string gets really big.
2199             int newBlockLength = Math.Max(minBlockCharCount, Math.Min(Length, MaxChunkSize));
2200
2201             // Move all of the data from this chunk to a new one, via a few O(1) pointer adjustments.
2202             // Then, have this chunk point to the new one as its predecessor.
2203             m_ChunkPrevious = new StringBuilder(this);
2204             m_ChunkOffset += m_ChunkLength;
2205             m_ChunkLength = 0;
2206
2207             // Check for integer overflow (logical buffer size > int.MaxValue)
2208             if (m_ChunkOffset + newBlockLength < newBlockLength)
2209             {
2210                 m_ChunkChars = null;
2211                 throw new OutOfMemoryException();
2212             }
2213             m_ChunkChars = new char[newBlockLength];
2214
2215             AssertInvariants();
2216         }
2217
2218         /// <summary>
2219         /// Creates a new chunk with fields copied from an existing chunk.
2220         /// </summary>
2221         /// <param name="from">The chunk from which to copy fields.</param>
2222         /// <remarks>
2223         /// <para>
2224         /// This method runs in O(1) time. It does not copy data within the character buffer
2225         /// <paramref name="from"/> holds, but copies the reference to the character buffer itself
2226         /// (plus a few other fields).
2227         /// </para>
2228         /// <para>
2229         /// Callers are expected to update <paramref name="from"/> subsequently to point to this
2230         /// chunk as its predecessor.
2231         /// </para>
2232         /// </remarks>
2233         private StringBuilder(StringBuilder from)
2234         {
2235             m_ChunkLength = from.m_ChunkLength;
2236             m_ChunkOffset = from.m_ChunkOffset;
2237             m_ChunkChars = from.m_ChunkChars;
2238             m_ChunkPrevious = from.m_ChunkPrevious;
2239             m_MaxCapacity = from.m_MaxCapacity;
2240
2241             AssertInvariants();
2242         }
2243
2244         /// <summary>
2245         /// Creates a gap at a logical index with the specified count.
2246         /// </summary>
2247         /// <param name="index">The logical index in this builder.</param>
2248         /// <param name="count">The number of characters in the gap.</param>
2249         /// <param name="chunk">Receives the chunk containing the gap.</param>
2250         /// <param name="indexInChunk">The index in <paramref name="chunk"/> that points to the gap.</param>
2251         /// <param name="doNotMoveFollowingChars">
2252         /// - If <c>true</c>, then room must be made by inserting a chunk before the current chunk.
2253         /// - If <c>false</c>, then room can be made by shifting characters ahead of <paramref name="index"/>
2254         ///   in this block forward by <paramref name="count"/> provided the characters will still fit in
2255         ///   the current chunk after being shifted.
2256         ///   - Providing <c>false</c> does not make a difference most of the time, but it can matter when someone
2257         ///     inserts lots of small strings at a position in the buffer.
2258         /// </param>
2259         /// <remarks>
2260         /// <para>
2261         /// Since chunks do not contain references to their successors, it is not always possible for us to make room
2262         /// by inserting space after <paramref name="index"/> in case this chunk runs out of space. Thus, we make room
2263         /// by inserting space before the specified index, and having logical indices refer to new locations by the end
2264         /// of this method.
2265         /// </para>
2266         /// <para>
2267         /// <see cref="ReplaceInPlaceAtChunk"/> can be used in conjunction with this method to fill in the newly created gap.
2268         /// </para>
2269         /// </remarks>
2270         private void MakeRoom(int index, int count, out StringBuilder chunk, out int indexInChunk, bool doNotMoveFollowingChars)
2271         {
2272             AssertInvariants();
2273             Debug.Assert(count > 0);
2274             Debug.Assert(index >= 0);
2275
2276             if (count + Length > m_MaxCapacity || count + Length < count)
2277             {
2278                 throw new ArgumentOutOfRangeException("requiredLength", SR.ArgumentOutOfRange_SmallCapacity);
2279             }
2280
2281             chunk = this;
2282             while (chunk.m_ChunkOffset > index)
2283             {
2284                 chunk.m_ChunkOffset += count;
2285                 chunk = chunk.m_ChunkPrevious;
2286             }
2287             indexInChunk = index - chunk.m_ChunkOffset;
2288
2289             // Cool, we have some space in this block, and we don't have to copy much to get at it, so go ahead and use it.
2290             // This typically happens when someone repeatedly inserts small strings at a spot (usually the absolute front) of the buffer.
2291             if (!doNotMoveFollowingChars && chunk.m_ChunkLength <= DefaultCapacity * 2 && chunk.m_ChunkChars.Length - chunk.m_ChunkLength >= count)
2292             {
2293                 for (int i = chunk.m_ChunkLength; i > indexInChunk; )
2294                 {
2295                     --i;
2296                     chunk.m_ChunkChars[i + count] = chunk.m_ChunkChars[i];
2297                 }
2298                 chunk.m_ChunkLength += count;
2299                 return;
2300             }
2301
2302             // Allocate space for the new chunk, which will go before the current one.
2303             StringBuilder newChunk = new StringBuilder(Math.Max(count, DefaultCapacity), chunk.m_MaxCapacity, chunk.m_ChunkPrevious);
2304             newChunk.m_ChunkLength = count;
2305
2306             // Copy the head of the current buffer to the new buffer.
2307             int copyCount1 = Math.Min(count, indexInChunk);
2308             if (copyCount1 > 0)
2309             {
2310                 unsafe
2311                 {
2312                     fixed (char* chunkCharsPtr = &chunk.m_ChunkChars[0])
2313                     {
2314                         ThreadSafeCopy(chunkCharsPtr, newChunk.m_ChunkChars, 0, copyCount1);
2315
2316                         // Slide characters over in the current buffer to make room.
2317                         int copyCount2 = indexInChunk - copyCount1;
2318                         if (copyCount2 >= 0)
2319                         {
2320                             ThreadSafeCopy(chunkCharsPtr + copyCount1, chunk.m_ChunkChars, 0, copyCount2);
2321                             indexInChunk = copyCount2;
2322                         }
2323                     }
2324                 }
2325             }
2326
2327             // Wire in the new chunk.
2328             chunk.m_ChunkPrevious = newChunk;
2329             chunk.m_ChunkOffset += count;
2330             if (copyCount1 < count)
2331             {
2332                 chunk = newChunk;
2333                 indexInChunk = copyCount1;
2334             }
2335
2336             AssertInvariants();
2337         }
2338
2339         /// <summary>
2340         /// Used by <see cref="MakeRoom"/> to allocate another chunk.
2341         /// </summary>
2342         /// <param name="size">The size of the character buffer for this chunk.</param>
2343         /// <param name="maxCapacity">The maximum capacity, to be stored in this chunk.</param>
2344         /// <param name="previousBlock">The predecessor of this chunk.</param>
2345         private StringBuilder(int size, int maxCapacity, StringBuilder previousBlock)
2346         {
2347             Debug.Assert(size > 0);
2348             Debug.Assert(maxCapacity > 0);
2349
2350             m_ChunkChars = new char[size];
2351             m_MaxCapacity = maxCapacity;
2352             m_ChunkPrevious = previousBlock;
2353             if (previousBlock != null)
2354             {
2355                 m_ChunkOffset = previousBlock.m_ChunkOffset + previousBlock.m_ChunkLength;
2356             }
2357
2358             AssertInvariants();
2359         }
2360
2361         /// <summary>
2362         /// Removes a specified number of characters beginning at a logical index in this builder.
2363         /// </summary>
2364         /// <param name="startIndex">The logical index in this builder to start removing characters.</param>
2365         /// <param name="count">The number of characters to remove.</param>
2366         /// <param name="chunk">Receives the new chunk containing the logical index.</param>
2367         /// <param name="indexInChunk">
2368         /// Receives the new index in <paramref name="chunk"/> that is associated with the logical index.
2369         /// </param>
2370         private void Remove(int startIndex, int count, out StringBuilder chunk, out int indexInChunk)
2371         {
2372             AssertInvariants();
2373             Debug.Assert(startIndex >= 0 && startIndex < Length);
2374
2375             int endIndex = startIndex + count;
2376
2377             // Find the chunks for the start and end of the block to delete. 
2378             chunk = this;
2379             StringBuilder endChunk = null;
2380             int endIndexInChunk = 0;
2381             for (;;)
2382             {
2383                 if (endIndex - chunk.m_ChunkOffset >= 0)
2384                 {
2385                     if (endChunk == null)
2386                     {
2387                         endChunk = chunk;
2388                         endIndexInChunk = endIndex - endChunk.m_ChunkOffset;
2389                     }
2390                     if (startIndex - chunk.m_ChunkOffset >= 0)
2391                     {
2392                         indexInChunk = startIndex - chunk.m_ChunkOffset;
2393                         break;
2394                     }
2395                 }
2396                 else
2397                 {
2398                     chunk.m_ChunkOffset -= count;
2399                 }
2400                 chunk = chunk.m_ChunkPrevious;
2401             }
2402             Debug.Assert(chunk != null, "We fell off the beginning of the string!");
2403
2404             int copyTargetIndexInChunk = indexInChunk;
2405             int copyCount = endChunk.m_ChunkLength - endIndexInChunk;
2406             if (endChunk != chunk)
2407             {
2408                 copyTargetIndexInChunk = 0;
2409                 // Remove the characters after `startIndex` to the end of the chunk.
2410                 chunk.m_ChunkLength = indexInChunk;
2411
2412                 // Remove the characters in chunks between the start and the end chunk.
2413                 endChunk.m_ChunkPrevious = chunk;
2414                 endChunk.m_ChunkOffset = chunk.m_ChunkOffset + chunk.m_ChunkLength;
2415
2416                 // If the start is 0, then we can throw away the whole start chunk.
2417                 if (indexInChunk == 0)
2418                 {
2419                     endChunk.m_ChunkPrevious = chunk.m_ChunkPrevious;
2420                     chunk = endChunk;
2421                 }
2422             }
2423             endChunk.m_ChunkLength -= (endIndexInChunk - copyTargetIndexInChunk);
2424
2425             // SafeCritical: We ensure that `endIndexInChunk + copyCount` is within range of `m_ChunkChars`, and
2426             // also ensure that `copyTargetIndexInChunk + copyCount` is within the chunk.
2427
2428             // Remove any characters in the end chunk, by sliding the characters down. 
2429             if (copyTargetIndexInChunk != endIndexInChunk) // Sometimes no move is necessary
2430             {
2431                 ThreadSafeCopy(endChunk.m_ChunkChars, endIndexInChunk, endChunk.m_ChunkChars, copyTargetIndexInChunk, copyCount);
2432             }
2433
2434             Debug.Assert(chunk != null, "We fell off the beginning of the string!");
2435             AssertInvariants();
2436         }
2437     }
2438 }