// The methods contained within this class do not return a new StringBuilder
// object unless specified otherwise. This class may be used in conjunction with the String
// class to carry out modifications upon strings.
- //
- // When passing null into a constructor in VJ and VC, the null
- // should be explicitly type cast.
- // For Example:
- // StringBuilder sb1 = new StringBuilder((StringBuilder)null);
- // StringBuilder sb2 = new StringBuilder((String)null);
- // Console.WriteLine(sb1);
- // Console.WriteLine(sb2);
- //
[Serializable]
[System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public sealed partial class StringBuilder : ISerializable
// a chunk of the string. It turns out string as a whole can also be represented as just a chunk,
// so that is what we do.
- //
- //
- // CLASS VARIABLES
- //
- //
- internal char[] m_ChunkChars; // The characters in this block
- internal StringBuilder m_ChunkPrevious; // Link to the block logically before this block
- internal int m_ChunkLength; // The index in m_ChunkChars that represent the end of the block
- internal int m_ChunkOffset; // The logical offset (sum of all characters in previous blocks)
- internal int m_MaxCapacity = 0;
-
- //
- //
- // STATIC CONSTANTS
- //
- //
+ /// <summary>
+ /// The character buffer for this chunk.
+ /// </summary>
+ internal char[] m_ChunkChars;
+
+ /// <summary>
+ /// The chunk that logically precedes this chunk.
+ /// </summary>
+ internal StringBuilder m_ChunkPrevious;
+
+ /// <summary>
+ /// The number of characters in this chunk.
+ /// This is the number of elements in <see cref="m_ChunkChars"/> that are in use, from the start of the buffer.
+ /// </summary>
+ internal int m_ChunkLength;
+
+ /// <summary>
+ /// The logical offset of this chunk's characters in the string it is a part of.
+ /// This is the sum of the number of characters in preceding blocks.
+ /// </summary>
+ internal int m_ChunkOffset;
+
+ /// <summary>
+ /// The maximum capacity this builder is allowed to have.
+ /// </summary>
+ internal int m_MaxCapacity;
+
+ /// <summary>
+ /// The default capacity of a <see cref="StringBuilder"/>.
+ /// </summary>
internal const int DefaultCapacity = 16;
- private const String CapacityField = "Capacity"; // Do not rename (binary serialization)
- private const String MaxCapacityField = "m_MaxCapacity"; // Do not rename (binary serialization)
- private const String StringValueField = "m_StringValue"; // Do not rename (binary serialization)
- private const String ThreadIDField = "m_currentThread"; // Do not rename (binary serialization)
+
+ private const string CapacityField = "Capacity"; // Do not rename (binary serialization)
+ private const string MaxCapacityField = "m_MaxCapacity"; // Do not rename (binary serialization)
+ private const string StringValueField = "m_StringValue"; // Do not rename (binary serialization)
+ private const string ThreadIDField = "m_currentThread"; // Do not rename (binary serialization)
// We want to keep chunk arrays out of large object heap (< 85K bytes ~ 40K chars) to be sure.
// Making the maximum chunk size big means less allocation code called, but also more waste
// within a buffer).
internal const int MaxChunkSize = 8000;
- //
- //
- //CONSTRUCTORS
- //
- //
-
- // Creates a new empty string builder (i.e., it represents String.Empty)
- // with the default capacity (16 characters).
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
public StringBuilder()
{
m_MaxCapacity = int.MaxValue;
m_ChunkChars = new char[DefaultCapacity];
}
- // Create a new empty string builder (i.e., it represents String.Empty)
- // with the specified capacity.
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="capacity">The initial capacity of this builder.</param>
public StringBuilder(int capacity)
: this(capacity, int.MaxValue)
{
}
- // Creates a new string builder from the specified string. If value
- // is a null String (i.e., if it represents String.NullString)
- // then the new string builder will also be null (i.e., it will also represent
- // String.NullString).
- //
- public StringBuilder(String value)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="value">The initial contents of this builder.</param>
+ public StringBuilder(string value)
: this(value, DefaultCapacity)
{
}
- // Creates a new string builder from the specified string with the specified
- // capacity. If value is a null String (i.e., if it represents
- // String.NullString) then the new string builder will also be null
- // (i.e., it will also represent String.NullString).
- // The maximum number of characters this string may contain is set by capacity.
- //
- public StringBuilder(String value, int capacity)
- : this(value, 0, ((value != null) ? value.Length : 0), capacity)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="value">The initial contents of this builder.</param>
+ /// <param name="capacity">The initial capacity of this builder.</param>
+ public StringBuilder(string value, int capacity)
+ : this(value, 0, value?.Length ?? 0, capacity)
{
}
- // Creates a new string builder from the specifed substring with the specified
- // capacity. The maximum number of characters is set by capacity.
- //
- public StringBuilder(String value, int startIndex, int length, int capacity)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="value">The initial contents of this builder.</param>
+ /// <param name="startIndex">The index to start in <paramref name="value"/>.</param>
+ /// <param name="length">The number of characters to read in <paramref name="value"/>.</param>
+ /// <param name="capacity">The initial capacity of this builder.</param>
+ public StringBuilder(string value, int startIndex, int length, int capacity)
{
if (capacity < 0)
{
- throw new ArgumentOutOfRangeException(nameof(capacity),
- SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(capacity)));
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(capacity)));
}
if (length < 0)
{
- throw new ArgumentOutOfRangeException(nameof(length),
- SR.Format(SR.ArgumentOutOfRange_MustBeNonNegNum, nameof(length)));
+ throw new ArgumentOutOfRangeException(nameof(length), SR.Format(SR.ArgumentOutOfRange_MustBeNonNegNum, nameof(length)));
}
if (startIndex < 0)
{
if (value == null)
{
- value = String.Empty;
+ value = string.Empty;
}
if (startIndex > value.Length - length)
{
throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_IndexLength);
}
- m_MaxCapacity = Int32.MaxValue;
+
+ m_MaxCapacity = int.MaxValue;
if (capacity == 0)
{
capacity = DefaultCapacity;
}
- if (capacity < length)
- capacity = length;
+ capacity = Math.Max(capacity, length);
m_ChunkChars = new char[capacity];
m_ChunkLength = length;
unsafe
{
fixed (char* sourcePtr = value)
+ {
ThreadSafeCopy(sourcePtr + startIndex, m_ChunkChars, 0, length);
+ }
}
}
- // Creates an empty StringBuilder with a minimum capacity of capacity
- // and a maximum capacity of maxCapacity.
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StringBuilder"/> class.
+ /// </summary>
+ /// <param name="capacity">The initial capacity of this builder.</param>
+ /// <param name="maxCapacity">The maximum capacity of this builder.</param>
public StringBuilder(int capacity, int maxCapacity)
{
if (capacity > maxCapacity)
}
if (capacity < 0)
{
- throw new ArgumentOutOfRangeException(nameof(capacity),
- SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(capacity)));
+ throw new ArgumentOutOfRangeException(nameof(capacity), SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(capacity)));
}
Contract.EndContractBlock();
private StringBuilder(SerializationInfo info, StreamingContext context)
{
if (info == null)
+ {
throw new ArgumentNullException(nameof(info));
+ }
Contract.EndContractBlock();
int persistedCapacity = 0;
capacityPresent = true;
break;
default:
- // Ignore other fields for forward compatibility.
+ // Ignore other fields for forwards-compatibility.
break;
}
}
// Check values and set defaults
if (persistedString == null)
{
- persistedString = String.Empty;
+ persistedString = string.Empty;
}
if (persistedMaxCapacity < 1 || persistedString.Length > persistedMaxCapacity)
{
if (!capacityPresent)
{
// StringBuilder in V1.X did not persist the Capacity, so this is a valid legacy code path.
- persistedCapacity = DefaultCapacity;
- if (persistedCapacity < persistedString.Length)
- {
- persistedCapacity = persistedString.Length;
- }
- if (persistedCapacity > persistedMaxCapacity)
- {
- persistedCapacity = persistedMaxCapacity;
- }
+ persistedCapacity = Math.Min(Math.Max(DefaultCapacity, persistedString.Length), persistedMaxCapacity);
}
+
if (persistedCapacity < 0 || persistedCapacity < persistedString.Length || persistedCapacity > persistedMaxCapacity)
{
throw new SerializationException(SR.Serialization_StringBuilderCapacity);
persistedString.CopyTo(0, m_ChunkChars, 0, persistedString.Length);
m_ChunkLength = persistedString.Length;
m_ChunkPrevious = null;
- VerifyClassInvariant();
+ AssertInvariants();
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
}
Contract.EndContractBlock();
- VerifyClassInvariant();
+ AssertInvariants();
info.AddValue(MaxCapacityField, m_MaxCapacity);
info.AddValue(CapacityField, Capacity);
info.AddValue(StringValueField, ToString());
}
[System.Diagnostics.Conditional("_DEBUG")]
- private void VerifyClassInvariant()
+ private void AssertInvariants()
{
- Debug.Assert((uint)(m_ChunkOffset + m_ChunkChars.Length) >= m_ChunkOffset, "Integer Overflow");
+ Debug.Assert(m_ChunkOffset + m_ChunkChars.Length >= m_ChunkOffset, "The length of the string is greater than int.MaxValue.");
+
StringBuilder currentBlock = this;
int maxCapacity = this.m_MaxCapacity;
for (;;)
{
- // All blocks have copy of the maxCapacity.
- Debug.Assert(currentBlock.m_MaxCapacity == maxCapacity, "Bad maxCapacity");
- Debug.Assert(currentBlock.m_ChunkChars != null, "Empty Buffer");
+ // All blocks have the same max capacity.
+ Debug.Assert(currentBlock.m_MaxCapacity == maxCapacity);
+ Debug.Assert(currentBlock.m_ChunkChars != null);
- Debug.Assert(currentBlock.m_ChunkLength <= currentBlock.m_ChunkChars.Length, "Out of range length");
- Debug.Assert(currentBlock.m_ChunkLength >= 0, "Negative length");
- Debug.Assert(currentBlock.m_ChunkOffset >= 0, "Negative offset");
+ Debug.Assert(currentBlock.m_ChunkLength <= currentBlock.m_ChunkChars.Length);
+ Debug.Assert(currentBlock.m_ChunkLength >= 0);
+ Debug.Assert(currentBlock.m_ChunkOffset >= 0);
StringBuilder prevBlock = currentBlock.m_ChunkPrevious;
if (prevBlock == null)
{
- Debug.Assert(currentBlock.m_ChunkOffset == 0, "First chunk's offset is not 0");
+ Debug.Assert(currentBlock.m_ChunkOffset == 0);
break;
}
- // There are no gaps in the blocks.
- Debug.Assert(currentBlock.m_ChunkOffset == prevBlock.m_ChunkOffset + prevBlock.m_ChunkLength, "There is a gap between chunks!");
+ // There are no gaps in the blocks.
+ Debug.Assert(currentBlock.m_ChunkOffset == prevBlock.m_ChunkOffset + prevBlock.m_ChunkLength);
currentBlock = prevBlock;
}
}
}
}
- public int MaxCapacity
- {
- get { return m_MaxCapacity; }
- }
+ /// <summary>
+ /// Gets the maximum capacity this builder is allowed to have.
+ /// </summary>
+ public int MaxCapacity => m_MaxCapacity;
- // Ensures that the capacity of this string builder is at least the specified value.
- // If capacity is greater than the capacity of this string builder, then the capacity
- // is set to capacity; otherwise the capacity is unchanged.
- //
+ /// <summary>
+ /// Ensures that the capacity of this builder is at least the specified value.
+ /// </summary>
+ /// <param name="capacity">The new capacity for this builder.</param>
+ /// <remarks>
+ /// If <paramref name="capacity"/> is less than or equal to the current capacity of
+ /// this builder, the capacity remains unchanged.
+ /// </remarks>
public int EnsureCapacity(int capacity)
{
if (capacity < 0)
public override String ToString()
{
- Contract.Ensures(Contract.Result<String>() != null);
-
- VerifyClassInvariant();
+ AssertInvariants();
if (Length == 0)
- return String.Empty;
+ {
+ return string.Empty;
+ }
- string ret = string.FastAllocateString(Length);
+ string result = string.FastAllocateString(Length);
StringBuilder chunk = this;
unsafe
{
- fixed (char* destinationPtr = ret)
+ fixed (char* destinationPtr = result)
{
do
{
int chunkLength = chunk.m_ChunkLength;
// Check that we will not overrun our boundaries.
- if ((uint)(chunkLength + chunkOffset) <= (uint)ret.Length && (uint)chunkLength <= (uint)sourceArray.Length)
+ if ((uint)(chunkLength + chunkOffset) <= (uint)result.Length && (uint)chunkLength <= (uint)sourceArray.Length)
{
fixed (char* sourcePtr = &sourceArray[0])
string.wstrcpy(destinationPtr + chunkOffset, sourcePtr, chunkLength);
}
}
chunk = chunk.m_ChunkPrevious;
- } while (chunk != null);
+ }
+ while (chunk != null);
- return ret;
+ return result;
}
}
}
-
- // Converts a substring of this string builder to a String.
- public String ToString(int startIndex, int length)
+ /// <summary>
+ /// Creates a string from a substring of this builder.
+ /// </summary>
+ /// <param name="startIndex">The index to start in this builder.</param>
+ /// <param name="length">The number of characters to read in this builder.</param>
+ public string ToString(int startIndex, int length)
{
Contract.Ensures(Contract.Result<String>() != null);
{
throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
}
- if (startIndex > (currentLength - length))
+ if (startIndex > currentLength - length)
{
throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_IndexLength);
}
- VerifyClassInvariant();
+ AssertInvariants();
StringBuilder chunk = this;
int sourceEndIndex = startIndex + length;
- string ret = string.FastAllocateString(length);
+ string result = string.FastAllocateString(length);
int curDestIndex = length;
unsafe
{
- fixed (char* destinationPtr = ret)
+ fixed (char* destinationPtr = result)
{
while (curDestIndex > 0)
{
int chunkEndIndex = sourceEndIndex - chunk.m_ChunkOffset;
if (chunkEndIndex >= 0)
{
- if (chunkEndIndex > chunk.m_ChunkLength)
- chunkEndIndex = chunk.m_ChunkLength;
+ chunkEndIndex = Math.Min(chunkEndIndex, chunk.m_ChunkLength);
int countLeft = curDestIndex;
int chunkCount = countLeft;
if (chunkCount > 0)
{
- // work off of local variables so that they are stable even in the presence of race conditions
+ // Work off of local variables so that they are stable even in the presence of race conditions
char[] sourceArray = chunk.m_ChunkChars;
// Check that we will not overrun our boundaries.
chunk = chunk.m_ChunkPrevious;
}
- return ret;
+ return result;
}
}
}
- // Convenience method for sb.Length=0;
public StringBuilder Clear()
{
this.Length = 0;
return this;
}
- // Sets the length of the String in this buffer. If length is less than the current
- // instance, the StringBuilder is truncated. If length is greater than the current
- // instance, nulls are appended. The capacity is adjusted to be the same as the length.
-
+ /// <summary>
+ /// Gets or sets the length of this builder.
+ /// </summary>
public int Length
{
get
{
m_ChunkLength = 0;
m_ChunkOffset = 0;
- Debug.Assert(Capacity >= originalCapacity, "setting the Length should never decrease the Capacity");
+ Debug.Assert(Capacity >= originalCapacity);
return;
}
int delta = value - Length;
- // if the specified length is greater than the current length
if (delta > 0)
{
- // the end of the string value of the current StringBuilder object is padded with the Unicode NULL character
- Append('\0', delta); // We could improve on this, but who does this anyway?
+ // Pad ourselves with null characters.
+ Append('\0', delta);
}
- // if the specified length is less than or equal to the current length
else
{
StringBuilder chunk = FindChunkForIndex(value);
if (chunk != this)
{
- // we crossed a chunk boundary when reducing the Length, we must replace this middle-chunk with a new
- // larger chunk to ensure the original capacity is preserved
+ // We crossed a chunk boundary when reducing the Length. We must replace this middle-chunk with a new larger chunk,
+ // to ensure the original capacity is preserved.
int newLen = originalCapacity - chunk.m_ChunkOffset;
char[] newArray = new char[newLen];
- Debug.Assert(newLen > chunk.m_ChunkChars.Length, "the new chunk should be larger than the one it is replacing");
+ Debug.Assert(newLen > chunk.m_ChunkChars.Length, "The new chunk should be larger than the one it is replacing.");
Array.Copy(chunk.m_ChunkChars, 0, newArray, 0, chunk.m_ChunkLength);
m_ChunkChars = newArray;
m_ChunkOffset = chunk.m_ChunkOffset;
}
m_ChunkLength = value - chunk.m_ChunkOffset;
- VerifyClassInvariant();
+ AssertInvariants();
}
- Debug.Assert(Capacity >= originalCapacity, "setting the Length should never decrease the Capacity");
+ Debug.Assert(Capacity >= originalCapacity);
}
}
- [System.Runtime.CompilerServices.IndexerName("Chars")]
+ [IndexerName("Chars")]
public char this[int index]
{
get
if (indexInBlock >= 0)
{
if (indexInBlock >= chunk.m_ChunkLength)
+ {
throw new IndexOutOfRangeException();
+ }
return chunk.m_ChunkChars[indexInBlock];
}
chunk = chunk.m_ChunkPrevious;
if (chunk == null)
+ {
throw new IndexOutOfRangeException();
+ }
}
}
set
if (indexInBlock >= 0)
{
if (indexInBlock >= chunk.m_ChunkLength)
+ {
throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
chunk.m_ChunkChars[indexInBlock] = value;
return;
}
chunk = chunk.m_ChunkPrevious;
if (chunk == null)
+ {
throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
+ }
}
}
}
- // Appends a character at the end of this string builder. The capacity is adjusted as needed.
+ /// <summary>
+ /// Appends a character 0 or more times to the end of this builder.
+ /// </summary>
+ /// <param name="value">The character to append.</param>
+ /// <param name="repeatCount">The number of times to append <paramref name="value"/>.</param>
public StringBuilder Append(char value, int repeatCount)
{
if (repeatCount < 0)
throw new ArgumentOutOfRangeException(nameof(repeatCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
}
- int idx = m_ChunkLength;
+ int index = m_ChunkLength;
while (repeatCount > 0)
{
- if (idx < m_ChunkChars.Length)
+ if (index < m_ChunkChars.Length)
{
- m_ChunkChars[idx++] = value;
+ m_ChunkChars[index++] = value;
--repeatCount;
}
else
{
- m_ChunkLength = idx;
+ m_ChunkLength = index;
ExpandByABlock(repeatCount);
- Debug.Assert(m_ChunkLength == 0, "Expand should create a new block");
- idx = 0;
+ Debug.Assert(m_ChunkLength == 0);
+ index = 0;
}
}
- m_ChunkLength = idx;
- VerifyClassInvariant();
+
+ m_ChunkLength = index;
+ AssertInvariants();
return this;
}
- // Appends an array of characters at the end of this string builder. The capacity is adjusted as needed.
+ /// <summary>
+ /// Appends a range of characters to the end of this builder.
+ /// </summary>
+ /// <param name="value">The characters to append.</param>
+ /// <param name="startIndex">The index to start in <paramref name="value"/>.</param>
+ /// <param name="charCount">The number of characters to read in <paramref name="value"/>.</param>
public StringBuilder Append(char[] value, int startIndex, int charCount)
{
if (startIndex < 0)
{
return this;
}
+
throw new ArgumentNullException(nameof(value));
}
if (charCount > value.Length - startIndex)
{
return this;
}
+
unsafe
{
fixed (char* valueChars = &value[startIndex])
{
Append(valueChars, charCount);
-
return this;
}
}
}
- // Appends a copy of this string at the end of this string builder.
+ /// <summary>
+ /// Appends a string to the end of this builder.
+ /// </summary>
+ /// <param name="value">The string to append.</param>
public StringBuilder Append(String value)
{
Contract.Ensures(Contract.Result<StringBuilder>() != null);
if (value != null)
{
- // This is a hand specialization of the 'AppendHelper' code below.
- // We could have just called AppendHelper.
+ // We could have just called AppendHelper here; this is a hand-specialization of that code.
char[] chunkChars = m_ChunkChars;
int chunkLength = m_ChunkLength;
int valueLen = value.Length;
int newCurrentIndex = chunkLength + valueLen;
- if (newCurrentIndex < chunkChars.Length) // Use strictly < to avoid issue if count == 0, newIndex == length
+
+ if (newCurrentIndex < chunkChars.Length) // Use strictly < to avoid issues if count == 0, newIndex == length
{
if (valueLen <= 2)
{
{
fixed (char* valuePtr = value)
fixed (char* destPtr = &chunkChars[chunkLength])
+ {
string.wstrcpy(destPtr, valuePtr, valueLen);
+ }
}
}
+
m_ChunkLength = newCurrentIndex;
}
else
+ {
AppendHelper(value);
+ }
}
+
return this;
}
-
- // We put this fixed in its own helper to avoid the cost zero initing valueChars in the
+ // We put this fixed in its own helper to avoid the cost of zero-initing `valueChars` in the
// case we don't actually use it.
private void AppendHelper(string value)
{
unsafe
{
fixed (char* valueChars = value)
+ {
Append(valueChars, value.Length);
+ }
}
}
- // Appends a copy of the characters in value from startIndex to startIndex +
- // count at the end of this string builder.
- public StringBuilder Append(String value, int startIndex, int count)
+ /// <summary>
+ /// Appends part of a string to the end of this builder.
+ /// </summary>
+ /// <param name="value">The string to append.</param>
+ /// <param name="startIndex">The index to start in <paramref name="value"/>.</param>
+ /// <param name="count">The number of characters to read in <paramref name="value"/>.</param>
+ public StringBuilder Append(string value, int startIndex, int count)
{
if (startIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
}
-
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_GenericPositive);
}
Contract.Ensures(Contract.Result<StringBuilder>() != null);
- //If the value being added is null, eat the null
- //and return.
if (value == null)
{
if (startIndex == 0 && count == 0)
fixed (char* valueChars = value)
{
Append(valueChars + startIndex, count);
-
return this;
}
}
}
- public StringBuilder AppendLine()
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(Environment.NewLine);
- }
+ public StringBuilder AppendLine() => Append(Environment.NewLine);
public StringBuilder AppendLine(string value)
{
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
Append(value);
return Append(Environment.NewLine);
}
if (destinationIndex < 0)
{
- throw new ArgumentOutOfRangeException(nameof(destinationIndex),
- SR.Format(SR.ArgumentOutOfRange_MustBeNonNegNum, nameof(destinationIndex)));
+ throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.Format(SR.ArgumentOutOfRange_MustBeNonNegNum, nameof(destinationIndex)));
}
if (destinationIndex > destination.Length - count)
}
Contract.EndContractBlock();
- VerifyClassInvariant();
+ AssertInvariants();
StringBuilder chunk = this;
int sourceEndIndex = sourceIndex + count;
int chunkEndIndex = sourceEndIndex - chunk.m_ChunkOffset;
if (chunkEndIndex >= 0)
{
- if (chunkEndIndex > chunk.m_ChunkLength)
- chunkEndIndex = chunk.m_ChunkLength;
+ chunkEndIndex = Math.Min(chunkEndIndex, chunk.m_ChunkLength);
int chunkCount = count;
int chunkStartIndex = chunkEndIndex - count;
curDestIndex -= chunkCount;
count -= chunkCount;
- // SafeCritical: we ensure that chunkStartIndex + chunkCount are within range of m_chunkChars
- // as well as ensuring that curDestIndex + chunkCount are within range of destination
+ // We ensure that chunkStartIndex + chunkCount are within range of m_chunkChars as well as
+ // ensuring that curDestIndex + chunkCount are within range of destination
ThreadSafeCopy(chunk.m_ChunkChars, chunkStartIndex, destination, curDestIndex, chunkCount);
}
chunk = chunk.m_ChunkPrevious;
}
}
- // Inserts multiple copies of a string into this string builder at the specified position.
- // Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, this
- // string builder is not changed.
- //
+ /// <summary>
+ /// Inserts a string 0 or more times into this builder at the specified position.
+ /// </summary>
+ /// <param name="index">The index to insert in this builder.</param>
+ /// <param name="value">The string to insert.</param>
+ /// <param name="count">The number of times to insert the string.</param>
public StringBuilder Insert(int index, String value, int count)
{
if (count < 0)
Contract.Ensures(Contract.Result<StringBuilder>() != null);
Contract.EndContractBlock();
- //Range check the index.
int currentLength = Length;
if ((uint)index > (uint)currentLength)
{
throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
}
- //If value is null, empty or count is 0, do nothing. This is ECMA standard.
- if (value == null || value.Length == 0 || count == 0)
+ if (string.IsNullOrEmpty(value) || count == 0)
{
return this;
}
- //Ensure we don't insert more chars than we can hold, and we don't
- //have any integer overflow in our inserted characters.
+ // Ensure we don't insert more chars than we can hold, and we don't
+ // have any integer overflow in our new length.
long insertingChars = (long)value.Length * count;
if (insertingChars > MaxCapacity - this.Length)
{
throw new OutOfMemoryException();
}
- Debug.Assert(insertingChars + this.Length < Int32.MaxValue);
+ Debug.Assert(insertingChars + this.Length < int.MaxValue);
StringBuilder chunk;
int indexInChunk;
}
}
- // Removes the specified characters from this string builder.
- // The length of this string builder is reduced by
- // length, but the capacity is unaffected.
- //
+ /// <summary>
+ /// Removes a range of characters from this builder.
+ /// </summary>
+ /// <remarks>
+ /// This method does not reduce the capacity of this builder.
+ /// </remarks>
public StringBuilder Remove(int startIndex, int length)
{
if (length < 0)
if (Length == length && startIndex == 0)
{
- // Optimization. If we are deleting everything
Length = 0;
return this;
}
int indexInChunk;
Remove(startIndex, length, out chunk, out indexInChunk);
}
+
return this;
}
- // Appends a boolean to the end of this string builder.
- // The capacity is adjusted as needed.
- public StringBuilder Append(bool value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(bool value) => Append(value.ToString());
- // Appends an sbyte to this string builder.
- // The capacity is adjusted as needed.
[CLSCompliant(false)]
- public StringBuilder Append(sbyte value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(sbyte value) => Append(value.ToString());
- // Appends a ubyte to this string builder.
- // The capacity is adjusted as needed.
- public StringBuilder Append(byte value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(byte value) => Append(value.ToString());
- // Appends a character at the end of this string builder. The capacity is adjusted as needed.
public StringBuilder Append(char value)
{
Contract.Ensures(Contract.Result<StringBuilder>() != null);
if (m_ChunkLength < m_ChunkChars.Length)
+ {
m_ChunkChars[m_ChunkLength++] = value;
+ }
else
+ {
Append(value, 1);
+ }
+
return this;
}
- // Appends a short to this string builder.
- // The capacity is adjusted as needed.
- public StringBuilder Append(short value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(short value) => Append(value.ToString());
- // Appends an int to this string builder.
- // The capacity is adjusted as needed.
- public StringBuilder Append(int value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(int value) => Append(value.ToString());
- // Appends a long to this string builder.
- // The capacity is adjusted as needed.
- public StringBuilder Append(long value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(long value) => Append(value.ToString());
- // Appends a float to this string builder.
- // The capacity is adjusted as needed.
- public StringBuilder Append(float value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(float value) => Append(value.ToString());
- // Appends a double to this string builder.
- // The capacity is adjusted as needed.
- public StringBuilder Append(double value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(double value) => Append(value.ToString());
- public StringBuilder Append(decimal value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(decimal value) => Append(value.ToString());
- // Appends an ushort to this string builder.
- // The capacity is adjusted as needed.
[CLSCompliant(false)]
- public StringBuilder Append(ushort value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(ushort value) => Append(value.ToString());
- // Appends an uint to this string builder.
- // The capacity is adjusted as needed.
[CLSCompliant(false)]
- public StringBuilder Append(uint value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
+ public StringBuilder Append(uint value) => Append(value.ToString());
- // Appends an unsigned long to this string builder.
- // The capacity is adjusted as needed.
[CLSCompliant(false)]
- public StringBuilder Append(ulong value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Append(value.ToString());
- }
-
- // Appends an Object to this string builder.
- // The capacity is adjusted as needed.
- public StringBuilder Append(Object value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
+ public StringBuilder Append(ulong value) => Append(value.ToString());
- if (null == value)
- {
- //Appending null is now a no-op.
- return this;
- }
- return Append(value.ToString());
- }
+ public StringBuilder Append(object value) => (value == null) ? this : Append(value.ToString());
- // Appends all of the characters in value to the current instance.
public StringBuilder Append(char[] value)
{
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
-
- if (null != value && value.Length > 0)
+ if (value?.Length > 0)
{
unsafe
{
fixed (char* valueChars = &value[0])
+ {
Append(valueChars, value.Length);
+ }
}
}
return this;
}
-
#region AppendJoin
public unsafe StringBuilder AppendJoin(string separator, params object[] values)
{
return AppendJoinCore(&separator, 1, values);
}
-
private unsafe StringBuilder AppendJoinCore<T>(char* separator, int separatorLength, IEnumerable<T> values)
{
+ Debug.Assert(separator != null);
+ Debug.Assert(separatorLength >= 0);
+
if (values == null)
+ {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values);
+ }
- using (var en = values.GetEnumerator())
+ using (IEnumerator<T> en = values.GetEnumerator())
{
if (!en.MoveNext())
+ {
return this;
+ }
var value = en.Current;
if (value != null)
+ {
Append(value.ToString());
+ }
while (en.MoveNext())
{
Append(separator, separatorLength);
value = en.Current;
if (value != null)
+ {
Append(value.ToString());
+ }
}
}
return this;
private unsafe StringBuilder AppendJoinCore<T>(char* separator, int separatorLength, T[] values)
{
if (values == null)
+ {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values);
+ }
if (values.Length == 0)
+ {
return this;
+ }
if (values[0] != null)
+ {
Append(values[0].ToString());
+ }
- for (var i = 1; i < values.Length; i++)
+ for (int i = 1; i < values.Length; i++)
{
Append(separator, separatorLength);
if (values[i] != null)
+ {
Append(values[i].ToString());
+ }
}
return this;
}
#endregion
-
- /*====================================Insert====================================
- **
- ==============================================================================*/
-
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
public StringBuilder Insert(int index, String value)
{
if ((uint)index > (uint)Length)
return this;
}
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
- public StringBuilder Insert(int index, bool value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, bool value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
[CLSCompliant(false)]
- public StringBuilder Insert(int index, sbyte value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, sbyte value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
- public StringBuilder Insert(int index, byte value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, byte value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
- public StringBuilder Insert(int index, short value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, short value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
public StringBuilder Insert(int index, char value)
{
Contract.Ensures(Contract.Result<StringBuilder>() != null);
return this;
}
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
public StringBuilder Insert(int index, char[] value)
{
if ((uint)index > (uint)Length)
return this;
}
- // Returns a reference to the StringBuilder with charCount characters from
- // value inserted into the buffer at index. Existing characters are shifted
- // to make room for the new text and capacity is adjusted as required. If value is null, the StringBuilder
- // is unchanged. Characters are taken from value starting at position startIndex.
public StringBuilder Insert(int index, char[] value, int startIndex, int charCount)
{
Contract.Ensures(Contract.Result<StringBuilder>() != null);
throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index);
}
- //If they passed in a null char array, just jump out quickly.
if (value == null)
{
if (startIndex == 0 && charCount == 0)
throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
}
- //Range check the array.
if (startIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
return this;
}
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
- public StringBuilder Insert(int index, int value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, int value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
- public StringBuilder Insert(int index, long value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, long value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
- public StringBuilder Insert(int index, float value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, float value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with ; value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed.
- //
- public StringBuilder Insert(int index, double value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, double value) => Insert(index, value.ToString(), 1);
- public StringBuilder Insert(int index, decimal value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, decimal value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed.
- //
[CLSCompliant(false)]
- public StringBuilder Insert(int index, ushort value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, ushort value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed.
- //
[CLSCompliant(false)]
- public StringBuilder Insert(int index, uint value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, uint value) => Insert(index, value.ToString(), 1);
- // Returns a reference to the StringBuilder with value inserted into
- // the buffer at index. Existing characters are shifted to make room for the new text.
- // The capacity is adjusted as needed.
- //
[CLSCompliant(false)]
- public StringBuilder Insert(int index, ulong value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, ulong value) => Insert(index, value.ToString(), 1);
- // Returns a reference to this string builder with value inserted into
- // the buffer at index. Existing characters are shifted to make room for the
- // new text. The capacity is adjusted as needed. If value equals String.Empty, the
- // StringBuilder is not changed. No changes are made if value is null.
- //
- public StringBuilder Insert(int index, Object value)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- if (null == value)
- {
- return this;
- }
- return Insert(index, value.ToString(), 1);
- }
+ public StringBuilder Insert(int index, Object value) => (value == null) ? this : Insert(index, value.ToString(), 1);
- public StringBuilder AppendFormat(String format, Object arg0)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return AppendFormatHelper(null, format, new ParamsArray(arg0));
- }
+ public StringBuilder AppendFormat(String format, Object arg0) => AppendFormatHelper(null, format, new ParamsArray(arg0));
- public StringBuilder AppendFormat(String format, Object arg0, Object arg1)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return AppendFormatHelper(null, format, new ParamsArray(arg0, arg1));
- }
+ public StringBuilder AppendFormat(String format, Object arg0, Object arg1) => AppendFormatHelper(null, format, new ParamsArray(arg0, arg1));
- public StringBuilder AppendFormat(String format, Object arg0, Object arg1, Object arg2)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return AppendFormatHelper(null, format, new ParamsArray(arg0, arg1, arg2));
- }
+ public StringBuilder AppendFormat(String format, Object arg0, Object arg1, Object arg2) => AppendFormatHelper(null, format, new ParamsArray(arg0, arg1, arg2));
public StringBuilder AppendFormat(String format, params Object[] args)
{
{
// To preserve the original exception behavior, throw an exception about format if both
// args and format are null. The actual null check for format is in AppendFormatHelper.
- throw new ArgumentNullException((format == null) ? nameof(format) : nameof(args));
+ string paramName = (format == null) ? nameof(format) : nameof(args);
+ throw new ArgumentNullException(paramName);
}
Contract.Ensures(Contract.Result<String>() != null);
Contract.EndContractBlock();
return AppendFormatHelper(null, format, new ParamsArray(args));
}
- public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return AppendFormatHelper(provider, format, new ParamsArray(arg0));
- }
+ public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0) => AppendFormatHelper(provider, format, new ParamsArray(arg0));
- public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0, Object arg1)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return AppendFormatHelper(provider, format, new ParamsArray(arg0, arg1));
- }
+ public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0, Object arg1) => AppendFormatHelper(provider, format, new ParamsArray(arg0, arg1));
- public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0, Object arg1, Object arg2)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return AppendFormatHelper(provider, format, new ParamsArray(arg0, arg1, arg2));
- }
+ public StringBuilder AppendFormat(IFormatProvider provider, String format, Object arg0, Object arg1, Object arg2) => AppendFormatHelper(provider, format, new ParamsArray(arg0, arg1, arg2));
public StringBuilder AppendFormat(IFormatProvider provider, String format, params Object[] args)
{
{
// To preserve the original exception behavior, throw an exception about format if both
// args and format are null. The actual null check for format is in AppendFormatHelper.
- throw new ArgumentNullException((format == null) ? nameof(format) : nameof(args));
+ string paramName = (format == null) ? nameof(format) : nameof(args);
+ throw new ArgumentNullException(paramName);
}
Contract.Ensures(Contract.Result<String>() != null);
Contract.EndContractBlock();
throw new FormatException(SR.Format_InvalidString);
}
- // undocumented exclusive limits on the range for Argument Hole Index and Argument Hole Alignment.
- private const int Index_Limit = 1000000; // Note: 0 <= ArgIndex < Index_Limit
- private const int Width_Limit = 1000000; // Note: -Width_Limit < ArgAlign < Width_Limit
+ // Undocumented exclusive limits on the range for Argument Hole Index and Argument Hole Alignment.
+ private const int IndexLimit = 1000000; // Note: 0 <= ArgIndex < IndexLimit
+ private const int WidthLimit = 1000000; // Note: -WidthLimit < ArgAlign < WidthLimit
internal StringBuilder AppendFormatHelper(IFormatProvider provider, String format, ParamsArray args)
{
if (pos == len) FormatError();
ch = format[pos];
// so long as character is digit and value of the index is less than 1000000 ( index limit )
- } while (ch >= '0' && ch <= '9' && index < Index_Limit);
+ }
+ while (ch >= '0' && ch <= '9' && index < IndexLimit);
// If value of index is not within the range of the arguments passed in then error (Index out of range)
if (index >= args.Length) throw new FormatException(SR.Format_IndexOutOfRange);
if (pos == len) FormatError();
ch = format[pos];
// So long a current character is a digit and the value of width is less than 100000 ( width limit )
- } while (ch >= '0' && ch <= '9' && width < Width_Limit);
+ }
+ while (ch >= '0' && ch <= '9' && width < WidthLimit);
// end of parsing Argument Alignment
}
return this;
}
- // Returns a reference to the current StringBuilder with all instances of oldString
- // replaced with newString. If startIndex and count are specified,
- // we only replace strings completely contained in the range of startIndex to startIndex +
- // count. The strings to be replaced are checked on an ordinal basis (e.g. not culture aware). If
- // newValue is null, instances of oldValue are removed (e.g. replaced with nothing.).
- //
- public StringBuilder Replace(String oldValue, String newValue)
- {
- Contract.Ensures(Contract.Result<StringBuilder>() != null);
- return Replace(oldValue, newValue, 0, Length);
- }
+ /// <summary>
+ /// Replaces all instances of one string with another in this builder.
+ /// </summary>
+ /// <param name="oldValue">The string to replace.</param>
+ /// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
+ /// <remarks>
+ /// If <paramref name="newValue"/> is <c>null</c>, instances of <paramref name="oldValue"/>
+ /// are removed from this builder.
+ /// </remarks>
+ public StringBuilder Replace(String oldValue, String newValue) => Replace(oldValue, newValue, 0, Length);
+ /// <summary>
+ /// Determines if the contents of this builder are equal to the contents of another builder.
+ /// </summary>
+ /// <param name="sb">The other builder.</param>
public bool Equals(StringBuilder sb)
{
if (sb == null)
int sbChunkIndex = sbChunk.m_ChunkLength;
for (;;)
{
- // Decrement the pointer to the 'this' StringBuilder
--thisChunkIndex;
--sbChunkIndex;
thisChunkIndex = thisChunk.m_ChunkLength + thisChunkIndex;
}
- // Decrement the pointer to the 'this' StringBuilder
while (sbChunkIndex < 0)
{
sbChunk = sbChunk.m_ChunkPrevious;
}
}
+ /// <summary>
+ /// Replaces all instances of one string with another in part of this builder.
+ /// </summary>
+ /// <param name="oldValue">The string to replace.</param>
+ /// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
+ /// <param name="startIndex">The index to start in this builder.</param>
+ /// <param name="count">The number of characters to read in this builder.</param>
+ /// <remarks>
+ /// If <paramref name="newValue"/> is <c>null</c>, instances of <paramref name="oldValue"/>
+ /// are removed from this builder.
+ /// </remarks>
public StringBuilder Replace(String oldValue, String newValue, int startIndex, int count)
{
Contract.Ensures(Contract.Result<StringBuilder>() != null);
throw new ArgumentException(SR.Argument_EmptyName, nameof(oldValue));
}
- if (newValue == null)
- newValue = "";
+ newValue = newValue ?? string.Empty;
int deltaLength = newValue.Length - oldValue.Length;
- int[] replacements = null; // A list of replacement positions in a chunk to apply
+ int[] replacements = null; // A list of replacement positions in a chunk to apply
int replacementsCount = 0;
// Find the chunk, indexInChunk for the starting point
// Look for a match in the chunk,indexInChunk pointer
if (StartsWith(chunk, indexInChunk, count, oldValue))
{
- // Push it on my replacements array (with growth), we will do all replacements in a
+ // Push it on the replacements array (with growth), we will do all replacements in a
// given chunk in one operation below (see ReplaceAllInChunk) so we don't have to slide
- // many times.
+ // many times.
if (replacements == null)
+ {
replacements = new int[5];
+ }
else if (replacementsCount >= replacements.Length)
{
- Array.Resize(ref replacements, replacements.Length * 3 / 2 + 4); // grow by 1.5X but more in the beginning
+ Array.Resize(ref replacements, replacements.Length * 3 / 2 + 4); // Grow by ~1.5x, but more in the begining
}
replacements[replacementsCount++] = indexInChunk;
indexInChunk += oldValue.Length;
--count;
}
- if (indexInChunk >= chunk.m_ChunkLength || count == 0) // Have we moved out of the current chunk
+ if (indexInChunk >= chunk.m_ChunkLength || count == 0) // Have we moved out of the current chunk?
{
- // Replacing mutates the blocks, so we need to convert to logical index and back afterward.
+ // Replacing mutates the blocks, so we need to convert to a logical index and back afterwards.
int index = indexInChunk + chunk.m_ChunkOffset;
int indexBeforeAdjustment = index;
- // See if we accumulated any replacements, if so apply them
+ // See if we accumulated any replacements, if so apply them.
ReplaceAllInChunk(replacements, replacementsCount, chunk, oldValue.Length, newValue);
// The replacement has affected the logical index. Adjust it.
index += ((newValue.Length - oldValue.Length) * replacementsCount);
chunk = FindChunkForIndex(index);
indexInChunk = index - chunk.m_ChunkOffset;
- Debug.Assert(chunk != null || count == 0, "Chunks ended prematurely");
+ Debug.Assert(chunk != null || count == 0, "Chunks ended prematurely!");
}
}
- VerifyClassInvariant();
+
+ AssertInvariants();
return this;
}
- // Returns a StringBuilder with all instances of oldChar replaced with
- // newChar. The size of the StringBuilder is unchanged because we're only
- // replacing characters. If startIndex and count are specified, we
- // only replace characters in the range from startIndex to startIndex+count
- //
+ /// <summary>
+ /// Replaces all instances of one character with another in this builder.
+ /// </summary>
+ /// <param name="oldChar">The character to replace.</param>
+ /// <param name="newChar">The character to replace <paramref name="oldChar"/> with.</param>
public StringBuilder Replace(char oldChar, char newChar)
{
return Replace(oldChar, newChar, 0, Length);
}
+
+ /// <summary>
+ /// Replaces all instances of one character with another in this builder.
+ /// </summary>
+ /// <param name="oldChar">The character to replace.</param>
+ /// <param name="newChar">The character to replace <paramref name="oldChar"/> with.</param>
+ /// <param name="startIndex">The index to start in this builder.</param>
+ /// <param name="count">The number of characters to read in this builder.</param>
public StringBuilder Replace(char oldChar, char newChar, int startIndex, int count)
{
Contract.Ensures(Contract.Result<StringBuilder>() != null);
int endIndex = startIndex + count;
StringBuilder chunk = this;
+
for (;;)
{
int endIndexInChunk = endIndex - chunk.m_ChunkOffset;
break;
chunk = chunk.m_ChunkPrevious;
}
+
+ AssertInvariants();
return this;
}
/// <summary>
- /// Appends 'value' of length 'count' to the stringBuilder.
+ /// Appends a character buffer to this builder.
/// </summary>
+ /// <param name="value">The pointer to the start of the buffer.</param>
+ /// <param name="valueCount">The number of characters in the buffer.</param>
[CLSCompliant(false)]
public unsafe StringBuilder Append(char* value, int valueCount)
{
// Expand the builder to add another chunk.
int restLength = valueCount - firstLength;
ExpandByABlock(restLength);
- Debug.Assert(m_ChunkLength == 0, "Expand did not make a new block");
+ Debug.Assert(m_ChunkLength == 0, "A new block was not created.");
// Copy the second chunk
ThreadSafeCopy(value + firstLength, m_ChunkChars, 0, restLength);
m_ChunkLength = restLength;
}
- VerifyClassInvariant();
+ AssertInvariants();
return this;
}
/// <summary>
- /// Inserts 'value' of length 'cou
+ /// Inserts a character buffer into this builder at the specified position.
/// </summary>
- unsafe private void Insert(int index, char* value, int valueCount)
+ /// <param name="index">The index to insert in this builder.</param>
+ /// <param name="value">The pointer to the start of the buffer.</param>
+ /// <param name="valueCount">The number of characters in the buffer.</param>
+ private unsafe void Insert(int index, char* value, int valueCount)
{
if ((uint)index > (uint)Length)
{
}
/// <summary>
- /// 'replacements' is a list of index (relative to the begining of the 'chunk' to remove
- /// 'removeCount' characters and replace them with 'value'. This routine does all those
- /// replacements in bulk (and therefore very efficiently.
- /// with the string 'value'.
+ /// Replaces strings at specified indices with a new string in a chunk.
/// </summary>
+ /// <param name="replacements">The list of indices, relative to the beginning of the chunk, to remove at.</param>
+ /// <param name="replacementsCount">The number of replacements to make.</param>
+ /// <param name="sourceChunk">The source chunk.</param>
+ /// <param name="removeCount">The number of characters to remove at each replacement.</param>
+ /// <param name="value">The string to insert at each replacement.</param>
+ /// <remarks>
+ /// This routine is very efficient because it does replacements in bulk.
+ /// </remarks>
private void ReplaceAllInChunk(int[] replacements, int replacementsCount, StringBuilder sourceChunk, int removeCount, string value)
{
if (replacementsCount <= 0)
+ {
return;
+ }
unsafe
{
int gapStart = replacements[i] + removeCount;
i++;
if (i >= replacementsCount)
+ {
break;
+ }
int gapEnd = replacements[i];
Debug.Assert(gapStart < sourceChunk.m_ChunkChars.Length, "gap starts at end of buffer. Should not happen");
}
/// <summary>
- /// Returns true if the string that is starts at 'chunk' and 'indexInChunk, and has a logical
- /// length of 'count' starts with the string 'value'.
+ /// Returns a value indicating whether a substring of a builder starts with a specified prefix.
/// </summary>
+ /// <param name="chunk">The chunk in which the substring starts.</param>
+ /// <param name="indexInChunk">The index in <paramref name="chunk"/> at which the substring starts.</param>
+ /// <param name="count">The logical count of the substring.</param>
+ /// <param name="value">The prefix.</param>
private bool StartsWith(StringBuilder chunk, int indexInChunk, int count, string value)
{
for (int i = 0; i < value.Length; i++)
{
if (count == 0)
+ {
return false;
+ }
+
if (indexInChunk >= chunk.m_ChunkLength)
{
chunk = Next(chunk);
indexInChunk = 0;
}
- // See if there no match, break out of the inner for loop
if (value[i] != chunk.m_ChunkChars[indexInChunk])
+ {
return false;
+ }
indexInChunk++;
--count;
}
+
return true;
}
/// <summary>
- /// ReplaceInPlaceAtChunk is the logical equivalent of 'memcpy'. Given a chunk and ann index in
- /// that chunk, it copies in 'count' characters from 'value' and updates 'chunk, and indexInChunk to
- /// point at the end of the characters just copyied (thus you can splice in strings from multiple
- /// places by calling this mulitple times.
+ /// Replaces characters at a specified location with the contents of a character buffer.
+ /// This function is the logical equivalent of memcpy.
/// </summary>
+ /// <param name="chunk">
+ /// The chunk in which to start replacing characters.
+ /// Receieves the chunk in which character replacement ends.
+ /// </param>
+ /// <param name="indexInChunk">
+ /// The index in <paramref name="chunk"/> to start replacing characters at.
+ /// Receives the index at which character replacement ends.
+ /// </param>
+ /// <param name="value">The pointer to the start of the character buffer.</param>
+ /// <param name="count">The number of characters in the buffer.</param>
unsafe private void ReplaceInPlaceAtChunk(ref StringBuilder chunk, ref int indexInChunk, char* value, int count)
{
if (count != 0)
for (;;)
{
int lengthInChunk = chunk.m_ChunkLength - indexInChunk;
- Debug.Assert(lengthInChunk >= 0, "index not in chunk");
+ Debug.Assert(lengthInChunk >= 0, "Index isn't in the chunk.");
int lengthToCopy = Math.Min(lengthInChunk, count);
ThreadSafeCopy(value, chunk.m_ChunkChars, indexInChunk, lengthToCopy);
}
count -= lengthToCopy;
if (count == 0)
+ {
break;
+ }
value += lengthToCopy;
}
}
}
- /// <summary>
- /// We have to prevent modification off the end of an array.
- /// The only way to do this is to copy all interesting variables out of the heap and then do the
- /// bounds check. This is what we do here.
- /// </summary>
+ /// <remarks>
+ /// This method prevents out-of-bounds writes in the case a different thread updates a field in the builder just before a copy begins.
+ /// All interesting variables are copied out of the heap into the parameters of this method, and then bounds checks are run.
+ /// </remarks>
private static unsafe void ThreadSafeCopy(char* sourcePtr, char[] destination, int destinationIndex, int count)
{
if (count > 0)
}
/// <summary>
- /// Finds the chunk for the logical index (number of characters in the whole stringbuilder) 'index'
- /// YOu can then get the offset in this chunk by subtracting the m_BlockOffset field from 'index'
+ /// Gets the chunk corresponding to the logical index in this builder.
/// </summary>
- /// <param name="index"></param>
- /// <returns></returns>
+ /// <param name="index">The logical index in this builder.</param>
+ /// <remarks>
+ /// After calling this method, you can obtain the actual index within the chunk by
+ /// subtracting <see cref="m_ChunkOffset"/> from <paramref name="index"/>.
+ /// </remarks>
private StringBuilder FindChunkForIndex(int index)
{
- Debug.Assert(0 <= index && index <= Length, "index not in string");
+ Debug.Assert(0 <= index && index <= Length);
- StringBuilder ret = this;
- while (ret.m_ChunkOffset > index)
- ret = ret.m_ChunkPrevious;
+ StringBuilder result = this;
+ while (result.m_ChunkOffset > index)
+ {
+ result = result.m_ChunkPrevious;
+ }
- Debug.Assert(ret != null, "index not in string");
- return ret;
+ Debug.Assert(result != null);
+ return result;
}
/// <summary>
- /// Finds the chunk for the logical byte index 'byteIndex'
+ /// Gets the chunk corresponding to the logical byte index in this builder.
/// </summary>
- /// <param name="index"></param>
- /// <returns></returns>
+ /// <param name="byteIndex">The logical byte index in this builder.</param>
private StringBuilder FindChunkForByte(int byteIndex)
{
- Debug.Assert(0 <= byteIndex && byteIndex <= Length * sizeof(char), "Byte Index not in string");
+ Debug.Assert(0 <= byteIndex && byteIndex <= Length * sizeof(char));
- StringBuilder ret = this;
- while (ret.m_ChunkOffset * sizeof(char) > byteIndex)
- ret = ret.m_ChunkPrevious;
+ StringBuilder result = this;
+ while (result.m_ChunkOffset * sizeof(char) > byteIndex)
+ {
+ result = result.m_ChunkPrevious;
+ }
- Debug.Assert(ret != null, "Byte Index not in string");
- return ret;
+ Debug.Assert(result != null);
+ return result;
}
/// <summary>
- /// Finds the chunk that logically follows the 'chunk' chunk. Chunks only persist the pointer to
- /// the chunk that is logically before it, so this routine has to start at the this pointer (which
- /// is a assumed to point at the chunk representing the whole stringbuilder) and search
- /// until it finds the current chunk (thus is O(n)). So it is more expensive than a field fetch!
+ /// Finds the chunk that logically succeeds the specified chunk.
/// </summary>
- private StringBuilder Next(StringBuilder chunk)
- {
- if (chunk == this)
- return null;
- return FindChunkForIndex(chunk.m_ChunkOffset + chunk.m_ChunkLength);
- }
+ /// <param name="chunk">The chunk whose successor should be found.</param>
+ /// <remarks>
+ /// Each chunk only stores the pointer to its logical predecessor, so this routine has to start
+ /// from the 'this' pointer (which is assumed to represent the whole StringBuilder) and work its
+ /// way down until it finds the specified chunk (which is O(n)). Thus, it is more expensive than
+ /// a field fetch.
+ /// </remarks>
+ private StringBuilder Next(StringBuilder chunk) => chunk == this ? null : FindChunkForIndex(chunk.m_ChunkOffset + chunk.m_ChunkLength);
/// <summary>
- /// Assumes that 'this' is the last chunk in the list and that it is full. Upon return the 'this'
- /// block is updated so that it is a new block that has at least 'minBlockCharCount' characters.
- /// that can be used to copy characters into it.
+ /// Transfers the character buffer from this chunk to a new chunk, and allocates a new buffer with a minimum size for this chunk.
/// </summary>
+ /// <param name="minBlockCharCount">The minimum size of the new buffer to be allocated for this chunk.</param>
+ /// <remarks>
+ /// This method requires that the current chunk is full. Otherwise, there's no point in shifting the characters over.
+ /// It also assumes that 'this' is the last chunk in the linked list.
+ /// </remarks>
private void ExpandByABlock(int minBlockCharCount)
{
- Contract.Requires(Capacity == Length, "Expand expect to be called only when there is no space left"); // We are currently full
- Contract.Requires(minBlockCharCount > 0, "Expansion request must be positive");
+ Contract.Requires(Capacity == Length, $"{nameof(ExpandByABlock)} should only be called when there is no space left.");
+ Contract.Requires(minBlockCharCount > 0);
- VerifyClassInvariant();
+ AssertInvariants();
if ((minBlockCharCount + Length) > m_MaxCapacity || minBlockCharCount + Length < minBlockCharCount)
+ {
throw new ArgumentOutOfRangeException("requiredLength", SR.ArgumentOutOfRange_SmallCapacity);
+ }
- // Compute the length of the new block we need
- // We make the new chunk at least big enough for the current need (minBlockCharCount)
- // But also as big as the current length (thus doubling capacity), up to a maximum
- // (so we stay in the small object heap, and never allocate really big chunks even if
- // the string gets really big.
+ // - We always need to make the new chunk at least as big as was requested (`minBlockCharCount`).
+ // - We'd also prefer to make it at least at big as the current length (thus doubling capacity).
+ // - But this is only up to a maximum, so we stay in the small object heap, and never allocate
+ // really big chunks even if the string gets really big.
int newBlockLength = Math.Max(minBlockCharCount, Math.Min(Length, MaxChunkSize));
- // Copy the current block to the new block, and initialize this to point at the new buffer.
+ // Move all of the data from this chunk to a new one, via a few O(1) pointer adjustments.
+ // Then, have this chunk point to the new one as its predecessor.
m_ChunkPrevious = new StringBuilder(this);
m_ChunkOffset += m_ChunkLength;
m_ChunkLength = 0;
- // Check for integer overflow (logical buffer size > int.MaxInt)
+ // Check for integer overflow (logical buffer size > int.MaxValue)
if (m_ChunkOffset + newBlockLength < newBlockLength)
{
m_ChunkChars = null;
}
m_ChunkChars = new char[newBlockLength];
- VerifyClassInvariant();
+ AssertInvariants();
}
/// <summary>
- /// Used by ExpandByABlock to create a new chunk. The new chunk is a copied from 'from'
- /// In particular the buffer is shared. It is expected that 'from' chunk (which represents
- /// the whole list, is then updated to point to point to this new chunk.
+ /// Creates a new chunk with fields copied from an existing chunk.
/// </summary>
+ /// <param name="from">The chunk from which to copy fields.</param>
+ /// <remarks>
+ /// <para>
+ /// This method runs in O(1) time. It does not copy data within the character buffer
+ /// <paramref name="from"/> holds, but copies the reference to the character buffer itself
+ /// (plus a few other fields).
+ /// </para>
+ /// <para>
+ /// Callers are expected to update <paramref name="from"/> subsequently to point to this
+ /// chunk as its predecessor.
+ /// </para>
+ /// </remarks>
private StringBuilder(StringBuilder from)
{
m_ChunkLength = from.m_ChunkLength;
m_ChunkChars = from.m_ChunkChars;
m_ChunkPrevious = from.m_ChunkPrevious;
m_MaxCapacity = from.m_MaxCapacity;
- VerifyClassInvariant();
+
+ AssertInvariants();
}
/// <summary>
- /// Creates a gap of size 'count' at the logical offset (count of characters in the whole string
- /// builder) 'index'. It returns the 'chunk' and 'indexInChunk' which represents a pointer to
- /// this gap that was just created. You can then use 'ReplaceInPlaceAtChunk' to fill in the
- /// chunk
- ///
- /// ReplaceAllChunks relies on the fact that indexes above 'index' are NOT moved outside 'chunk'
- /// by this process (because we make the space by creating the cap BEFORE the chunk). If we
- /// change this ReplaceAllChunks needs to be updated.
- ///
- /// If dontMoveFollowingChars is true, then the room must be made by inserting a chunk BEFORE the
- /// current chunk (this is what it does most of the time anyway)
+ /// Creates a gap at a logical index with the specified count.
/// </summary>
- private void MakeRoom(int index, int count, out StringBuilder chunk, out int indexInChunk, bool doneMoveFollowingChars)
- {
- VerifyClassInvariant();
- Debug.Assert(count > 0, "Count must be strictly positive");
- Debug.Assert(index >= 0, "Index can't be negative");
+ /// <param name="index">The logical index in this builder.</param>
+ /// <param name="count">The number of characters in the gap.</param>
+ /// <param name="chunk">Receives the chunk containing the gap.</param>
+ /// <param name="indexInChunk">The index in <paramref name="chunk"/> that points to the gap.</param>
+ /// <param name="doNotMoveFollowingChars">
+ /// - If <c>true</c>, then room must be made by inserting a chunk before the current chunk.
+ /// - If <c>false</c>, then room can be made by shifting characters ahead of <paramref name="index"/>
+ /// in this block forward by <paramref name="count"/> provided the characters will still fit in
+ /// the current chunk after being shifted.
+ /// - Providing <c>false</c> does not make a difference most of the time, but it can matter when someone
+ /// inserts lots of small strings at a position in the buffer.
+ /// </param>
+ /// <remarks>
+ /// <para>
+ /// Since chunks do not contain references to their successors, it is not always possible for us to make room
+ /// by inserting space after <paramref name="index"/> in case this chunk runs out of space. Thus, we make room
+ /// by inserting space before the specified index, and having logical indices refer to new locations by the end
+ /// of this method.
+ /// </para>
+ /// <para>
+ /// <see cref="ReplaceInPlaceAtChunk"/> can be used in conjunction with this method to fill in the newly created gap.
+ /// </para>
+ /// </remarks>
+ private void MakeRoom(int index, int count, out StringBuilder chunk, out int indexInChunk, bool doNotMoveFollowingChars)
+ {
+ AssertInvariants();
+ Debug.Assert(count > 0);
+ Debug.Assert(index >= 0);
+
if (count + Length > m_MaxCapacity || count + Length < count)
+ {
throw new ArgumentOutOfRangeException("requiredLength", SR.ArgumentOutOfRange_SmallCapacity);
+ }
chunk = this;
while (chunk.m_ChunkOffset > index)
}
indexInChunk = index - chunk.m_ChunkOffset;
- // Cool, we have some space in this block, and you don't have to copy much to get it, go ahead
- // and use it. This happens typically when you repeatedly insert small strings at a spot
- // (typically the absolute front) of the buffer.
- if (!doneMoveFollowingChars && chunk.m_ChunkLength <= DefaultCapacity * 2 && chunk.m_ChunkChars.Length - chunk.m_ChunkLength >= count)
+ // 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.
+ // This typically happens when someone repeatedly inserts small strings at a spot (usually the absolute front) of the buffer.
+ if (!doNotMoveFollowingChars && chunk.m_ChunkLength <= DefaultCapacity * 2 && chunk.m_ChunkChars.Length - chunk.m_ChunkLength >= count)
{
- for (int i = chunk.m_ChunkLength; i > indexInChunk;)
+ for (int i = chunk.m_ChunkLength; i > indexInChunk; )
{
--i;
chunk.m_ChunkChars[i + count] = chunk.m_ChunkChars[i];
return;
}
- // Allocate space for the new chunk (will go before this one)
+ // Allocate space for the new chunk, which will go before the current one.
StringBuilder newChunk = new StringBuilder(Math.Max(count, DefaultCapacity), chunk.m_MaxCapacity, chunk.m_ChunkPrevious);
newChunk.m_ChunkLength = count;
- // Copy the head of the buffer to the new buffer.
+ // Copy the head of the current buffer to the new buffer.
int copyCount1 = Math.Min(count, indexInChunk);
if (copyCount1 > 0)
{
{
ThreadSafeCopy(chunkCharsPtr, newChunk.m_ChunkChars, 0, copyCount1);
- // Slide characters in the current buffer over to make room.
+ // Slide characters over in the current buffer to make room.
int copyCount2 = indexInChunk - copyCount1;
if (copyCount2 >= 0)
{
}
}
- chunk.m_ChunkPrevious = newChunk; // Wire in the new chunk
+ // Wire in the new chunk.
+ chunk.m_ChunkPrevious = newChunk;
chunk.m_ChunkOffset += count;
if (copyCount1 < count)
{
indexInChunk = copyCount1;
}
- VerifyClassInvariant();
+ AssertInvariants();
}
/// <summary>
- /// Used by MakeRoom to allocate another chunk.
+ /// Used by <see cref="MakeRoom"/> to allocate another chunk.
/// </summary>
+ /// <param name="size">The size of the character buffer for this chunk.</param>
+ /// <param name="maxCapacity">The maximum capacity, to be stored in this chunk.</param>
+ /// <param name="previousBlock">The predecessor of this chunk.</param>
private StringBuilder(int size, int maxCapacity, StringBuilder previousBlock)
{
- Debug.Assert(size > 0, "size not positive");
- Debug.Assert(maxCapacity > 0, "maxCapacity not positive");
+ Debug.Assert(size > 0);
+ Debug.Assert(maxCapacity > 0);
+
m_ChunkChars = new char[size];
m_MaxCapacity = maxCapacity;
m_ChunkPrevious = previousBlock;
if (previousBlock != null)
+ {
m_ChunkOffset = previousBlock.m_ChunkOffset + previousBlock.m_ChunkLength;
- VerifyClassInvariant();
+ }
+
+ AssertInvariants();
}
/// <summary>
- /// Removes 'count' characters from the logical index 'startIndex' and returns the chunk and
- /// index in the chunk of that logical index in the out parameters.
+ /// Removes a specified number of characters beginning at a logical index in this builder.
/// </summary>
+ /// <param name="startIndex">The logical index in this builder to start removing characters.</param>
+ /// <param name="count">The number of characters to remove.</param>
+ /// <param name="chunk">Receives the new chunk containing the logical index.</param>
+ /// <param name="indexInChunk">
+ /// Receives the new index in <paramref name="chunk"/> that is associated with the logical index.
+ /// </param>
private void Remove(int startIndex, int count, out StringBuilder chunk, out int indexInChunk)
{
- VerifyClassInvariant();
- Debug.Assert(startIndex >= 0 && startIndex < Length, "startIndex not in string");
+ AssertInvariants();
+ Debug.Assert(startIndex >= 0 && startIndex < Length);
int endIndex = startIndex + count;
}
chunk = chunk.m_ChunkPrevious;
}
- Debug.Assert(chunk != null, "fell off beginning of string!");
+ Debug.Assert(chunk != null, "We fell off the beginning of the string!");
int copyTargetIndexInChunk = indexInChunk;
int copyCount = endChunk.m_ChunkLength - endIndexInChunk;
if (endChunk != chunk)
{
copyTargetIndexInChunk = 0;
- // Remove the characters after startIndex to end of the chunk
+ // Remove the characters after `startIndex` to the end of the chunk.
chunk.m_ChunkLength = indexInChunk;
- // Remove the characters in chunks between start and end chunk
+ // Remove the characters in chunks between the start and the end chunk.
endChunk.m_ChunkPrevious = chunk;
endChunk.m_ChunkOffset = chunk.m_ChunkOffset + chunk.m_ChunkLength;
- // If the start is 0 then we can throw away the whole start chunk
+ // If the start is 0, then we can throw away the whole start chunk.
if (indexInChunk == 0)
{
endChunk.m_ChunkPrevious = chunk.m_ChunkPrevious;
}
endChunk.m_ChunkLength -= (endIndexInChunk - copyTargetIndexInChunk);
- // SafeCritical: We ensure that endIndexInChunk + copyCount is within range of m_ChunkChars and
- // also ensure that copyTargetIndexInChunk + copyCount is within the chunk
- //
+ // SafeCritical: We ensure that `endIndexInChunk + copyCount` is within range of `m_ChunkChars`, and
+ // also ensure that `copyTargetIndexInChunk + copyCount` is within the chunk.
+
// Remove any characters in the end chunk, by sliding the characters down.
- if (copyTargetIndexInChunk != endIndexInChunk) // Sometimes no move is necessary
+ if (copyTargetIndexInChunk != endIndexInChunk) // Sometimes no move is necessary
+ {
ThreadSafeCopy(endChunk.m_ChunkChars, endIndexInChunk, endChunk.m_ChunkChars, copyTargetIndexInChunk, copyCount);
+ }
- Debug.Assert(chunk != null, "fell off beginning of string!");
- VerifyClassInvariant();
+ Debug.Assert(chunk != null, "We fell off the beginning of the string!");
+ AssertInvariants();
}
}
}