@@ -21,7 +21,7 @@ namespace System.Text
2121 // class to carry out modifications upon strings.
2222 [ Serializable ]
2323 [ TypeForwardedFrom ( "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ) ]
24- public sealed partial class StringBuilder : ISerializable
24+ public sealed partial class StringBuilder : ISerializable , IStringBuilderInternal
2525 {
2626 // A StringBuilder is internally represented as a linked list of blocks each of which holds
2727 // a chunk of the string. It turns out string as a whole can also be represented as just a chunk,
@@ -1518,274 +1518,9 @@ public StringBuilder AppendFormat(IFormatProvider? provider, [StringSyntax(Strin
15181518 /// </exception>
15191519 public StringBuilder AppendFormat ( IFormatProvider ? provider , [ StringSyntax ( StringSyntaxAttribute . CompositeFormat ) ] string format , params ReadOnlySpan < object ? > args )
15201520 {
1521- ArgumentNullException . ThrowIfNull ( format ) ;
1522-
1523- // Undocumented exclusive limits on the range for Argument Hole Index and Argument Hole Alignment.
1524- const int IndexLimit = 1_000_000 ; // Note: 0 <= ArgIndex < IndexLimit
1525- const int WidthLimit = 1_000_000 ; // Note: -WidthLimit < ArgAlign < WidthLimit
1526-
1527- // Query the provider (if one was supplied) for an ICustomFormatter. If there is one,
1528- // it needs to be used to transform all arguments.
1529- ICustomFormatter ? cf = ( ICustomFormatter ? ) provider ? . GetFormat ( typeof ( ICustomFormatter ) ) ;
1530-
1531- // Repeatedly find the next hole and process it.
1532- int pos = 0 ;
1533- char ch ;
1534- while ( true )
1535- {
1536- // Skip until either the end of the input or the first unescaped opening brace, whichever comes first.
1537- // Along the way we need to also unescape escaped closing braces.
1538- while ( true )
1539- {
1540- // Find the next brace. If there isn't one, the remainder of the input is text to be appended, and we're done.
1541- if ( ( uint ) pos >= ( uint ) format . Length )
1542- {
1543- return this ;
1544- }
1545-
1546- ReadOnlySpan < char > remainder = format . AsSpan ( pos ) ;
1547- int countUntilNextBrace = remainder . IndexOfAny ( '{' , '}' ) ;
1548- if ( countUntilNextBrace < 0 )
1549- {
1550- Append ( remainder ) ;
1551- return this ;
1552- }
1553-
1554- // Append the text until the brace.
1555- Append ( remainder . Slice ( 0 , countUntilNextBrace ) ) ;
1556- pos += countUntilNextBrace ;
1557-
1558- // Get the brace. It must be followed by another character, either a copy of itself in the case of being
1559- // escaped, or an arbitrary character that's part of the hole in the case of an opening brace.
1560- char brace = format [ pos ] ;
1561- ch = MoveNext ( format , ref pos ) ;
1562- if ( brace == ch )
1563- {
1564- Append ( ch ) ;
1565- pos ++ ;
1566- continue ;
1567- }
1568-
1569- // This wasn't an escape, so it must be an opening brace.
1570- if ( brace != '{' )
1571- {
1572- ThrowHelper . ThrowFormatInvalidString ( pos , ExceptionResource . Format_UnexpectedClosingBrace ) ;
1573- }
1574-
1575- // Proceed to parse the hole.
1576- break ;
1577- }
1578-
1579- // We're now positioned just after the opening brace of an argument hole, which consists of
1580- // an opening brace, an index, an optional width preceded by a comma, and an optional format
1581- // preceded by a colon, with arbitrary amounts of spaces throughout.
1582- int width = 0 ;
1583- bool leftJustify = false ;
1584- ReadOnlySpan < char > itemFormatSpan = default ; // used if itemFormat is null
1585-
1586- // First up is the index parameter, which is of the form:
1587- // at least on digit
1588- // optional any number of spaces
1589- // We've already read the first digit into ch.
1590- Debug . Assert ( format [ pos - 1 ] == '{' ) ;
1591- Debug . Assert ( ch != '{' ) ;
1592- int index = ch - '0' ;
1593- if ( ( uint ) index >= 10u )
1594- {
1595- ThrowHelper . ThrowFormatInvalidString ( pos , ExceptionResource . Format_ExpectedAsciiDigit ) ;
1596- }
1597-
1598- // Common case is a single digit index followed by a closing brace. If it's not a closing brace,
1599- // proceed to finish parsing the full hole format.
1600- ch = MoveNext ( format , ref pos ) ;
1601- if ( ch != '}' )
1602- {
1603- // Continue consuming optional additional digits.
1604- while ( char . IsAsciiDigit ( ch ) && index < IndexLimit )
1605- {
1606- index = index * 10 + ch - '0' ;
1607- ch = MoveNext ( format , ref pos ) ;
1608- }
1609-
1610- // Consume optional whitespace.
1611- while ( ch == ' ' )
1612- {
1613- ch = MoveNext ( format , ref pos ) ;
1614- }
1615-
1616- // Parse the optional alignment, which is of the form:
1617- // comma
1618- // optional any number of spaces
1619- // optional -
1620- // at least one digit
1621- // optional any number of spaces
1622- if ( ch == ',' )
1623- {
1624- // Consume optional whitespace.
1625- do
1626- {
1627- ch = MoveNext ( format , ref pos ) ;
1628- }
1629- while ( ch == ' ' ) ;
1630-
1631- // Consume an optional minus sign indicating left alignment.
1632- if ( ch == '-' )
1633- {
1634- leftJustify = true ;
1635- ch = MoveNext ( format , ref pos ) ;
1636- }
1637-
1638- // Parse alignment digits. The read character must be a digit.
1639- width = ch - '0' ;
1640- if ( ( uint ) width >= 10u )
1641- {
1642- ThrowHelper . ThrowFormatInvalidString ( pos , ExceptionResource . Format_ExpectedAsciiDigit ) ;
1643- }
1644- ch = MoveNext ( format , ref pos ) ;
1645- while ( char . IsAsciiDigit ( ch ) && width < WidthLimit )
1646- {
1647- width = width * 10 + ch - '0' ;
1648- ch = MoveNext ( format , ref pos ) ;
1649- }
1650-
1651- // Consume optional whitespace
1652- while ( ch == ' ' )
1653- {
1654- ch = MoveNext ( format , ref pos ) ;
1655- }
1656- }
1657-
1658- // The next character needs to either be a closing brace for the end of the hole,
1659- // or a colon indicating the start of the format.
1660- if ( ch != '}' )
1661- {
1662- if ( ch != ':' )
1663- {
1664- // Unexpected character
1665- ThrowHelper . ThrowFormatInvalidString ( pos , ExceptionResource . Format_UnclosedFormatItem ) ;
1666- }
1667-
1668- // Search for the closing brace; everything in between is the format,
1669- // but opening braces aren't allowed.
1670- int startingPos = pos ;
1671- while ( true )
1672- {
1673- ch = MoveNext ( format , ref pos ) ;
1674-
1675- if ( ch == '}' )
1676- {
1677- // Argument hole closed
1678- break ;
1679- }
1680-
1681- if ( ch == '{' )
1682- {
1683- // Braces inside the argument hole are not supported
1684- ThrowHelper . ThrowFormatInvalidString ( pos , ExceptionResource . Format_UnclosedFormatItem ) ;
1685- }
1686- }
1687-
1688- startingPos ++ ;
1689- itemFormatSpan = format . AsSpan ( startingPos , pos - startingPos ) ;
1690- }
1691- }
1692-
1693- // Construct the output for this arg hole.
1694- Debug . Assert ( format [ pos ] == '}' ) ;
1695- pos ++ ;
1696- string ? s = null ;
1697- string ? itemFormat = null ;
1698-
1699- if ( ( uint ) index >= ( uint ) args . Length )
1700- {
1701- ThrowHelper . ThrowFormatIndexOutOfRange ( ) ;
1702- }
1703- object ? arg = args [ index ] ;
1704-
1705- if ( cf != null )
1706- {
1707- if ( ! itemFormatSpan . IsEmpty )
1708- {
1709- itemFormat = new string ( itemFormatSpan ) ;
1710- }
1711-
1712- s = cf . Format ( itemFormat , arg , provider ) ;
1713- }
1714-
1715- if ( s == null )
1716- {
1717- // If arg is ISpanFormattable and the beginning doesn't need padding,
1718- // try formatting it into the remaining current chunk.
1719- if ( ( leftJustify || width == 0 ) &&
1720- arg is ISpanFormattable spanFormattableArg &&
1721- spanFormattableArg . TryFormat ( RemainingCurrentChunk , out int charsWritten , itemFormatSpan , provider ) )
1722- {
1723- if ( ( uint ) charsWritten > ( uint ) RemainingCurrentChunk . Length )
1724- {
1725- // Untrusted ISpanFormattable implementations might return an erroneous charsWritten value,
1726- // and m_ChunkLength might end up being used in Unsafe code, so fail if we get back an
1727- // out-of-range charsWritten value.
1728- ThrowHelper . ThrowFormatInvalidString ( ) ;
1729- }
1730-
1731- m_ChunkLength += charsWritten ;
1732-
1733- // Pad the end, if needed.
1734- if ( leftJustify && width > charsWritten )
1735- {
1736- Append ( ' ' , width - charsWritten ) ;
1737- }
1738-
1739- // Continue to parse other characters.
1740- continue ;
1741- }
1742-
1743- // Otherwise, fallback to trying IFormattable or calling ToString.
1744- if ( arg is IFormattable formattableArg )
1745- {
1746- if ( itemFormatSpan . Length != 0 )
1747- {
1748- itemFormat ??= new string ( itemFormatSpan ) ;
1749- }
1750- s = formattableArg . ToString ( itemFormat , provider ) ;
1751- }
1752- else
1753- {
1754- s = arg ? . ToString ( ) ;
1755- }
1756-
1757- s ??= string . Empty ;
1758- }
1759-
1760- // Append it to the final output of the Format String.
1761- if ( width <= s . Length )
1762- {
1763- Append ( s ) ;
1764- }
1765- else if ( leftJustify )
1766- {
1767- Append ( s ) ;
1768- Append ( ' ' , width - s . Length ) ;
1769- }
1770- else
1771- {
1772- Append ( ' ' , width - s . Length ) ;
1773- Append ( s ) ;
1774- }
1775-
1776- // Continue parsing the rest of the format string.
1777- }
1778-
1779- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
1780- static char MoveNext ( string format , ref int pos )
1781- {
1782- pos ++ ;
1783- if ( ( uint ) pos >= ( uint ) format . Length )
1784- {
1785- ThrowHelper . ThrowFormatInvalidString ( pos , ExceptionResource . Format_UnclosedFormatItem ) ;
1786- }
1787- return format [ pos ] ;
1788- }
1521+ StringBuilder sb = this ;
1522+ StringBuilderInternal . AppendFormatHelper ( ref sb , provider , format , args ) ;
1523+ return this ;
17891524 }
17901525
17911526 /// <summary>
@@ -2798,6 +2533,12 @@ private void Remove(int startIndex, int count, out StringBuilder chunk, out int
27982533 AssertInvariants ( ) ;
27992534 }
28002535
2536+ Span < char > IStringBuilderInternal . RemainingCurrentChunk => RemainingCurrentChunk ;
2537+ void IStringBuilderInternal . Append ( char item ) => Append ( item ) ;
2538+ void IStringBuilderInternal . Append ( char item , int count ) => Append ( item , count ) ;
2539+ void IStringBuilderInternal . Append ( ReadOnlySpan < char > value ) => Append ( value ) ;
2540+ void IStringBuilderInternal . UnsafeGrow ( int size ) => m_ChunkLength += size ;
2541+
28012542 /// <summary>Provides a handler used by the language compiler to append interpolated strings into <see cref="StringBuilder"/> instances.</summary>
28022543 [ EditorBrowsable ( EditorBrowsableState . Never ) ]
28032544 [ InterpolatedStringHandler ]
0 commit comments