77
88namespace SmartFormat . Extensions ;
99
10+ #if NET6_0_OR_GREATER
1011/// <summary>
11- /// Do the default formatting, same logic as "String.Format".
12+ /// Does the default formatting.
13+ /// This formatter in always required, unless you implement your own.
14+ /// <pre/>
15+ /// It supports <see cref="ISpanFormattable"/>, <see cref="IFormattable"/>, and <see cref="ICustomFormatter"/>.
1216/// </summary>
17+ #else
18+ /// <summary>
19+ /// Does the default formatting.
20+ /// This formatter in always required, unless you implement your own.
21+ /// <pre/>
22+ /// It supports <see cref="IFormattable"/> and <see cref="ICustomFormatter"/>.
23+ /// </summary>
24+ #endif
1325public class DefaultFormatter : IFormatter
1426{
27+
28+ #if NET6_0_OR_GREATER
29+ /// <summary>
30+ /// The maximum size of the stack-allocated buffer
31+ /// for formatting <see cref="System.ISpanFormattable"/> objects.
32+ /// </summary>
33+ internal const int StackAllocCharBufferSize = 512 ;
34+ #endif
35+
1536 /// <summary>
1637 /// Obsolete. <see cref="IFormatter"/>s only have one unique name.
1738 /// </summary>
1839 [ Obsolete ( "Use property \" Name\" instead" , true ) ]
19- public string [ ] Names { get ; set ; } = { "default" , "d" , string . Empty } ;
40+ public string [ ] Names { get ; set ; } = { "default" , "d" , string . Empty } ;
2041
2142 ///<inheritdoc/>
2243 public string Name { get ; set ; } = "d" ;
@@ -42,37 +63,57 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
4263 return true ;
4364 }
4465
45- // Use the provider to see if a CustomFormatter is available:
46- var provider = formattingInfo . FormatDetails . Provider ;
66+ /*
67+ * The order of precedence is:
68+ * 1. ICustomFormatter from the IFormatProvider
69+ * 2. ISpanFormattable (for .NET 6.0 or later)
70+ * 3. IFormattable
71+ * 4. ToString
72+ */
4773
48- // We will try using IFormatProvider, IFormattable, and if all else fails, ToString.
49- string ? result ;
74+ var provider = formattingInfo . FormatDetails . Provider ;
5075 if ( provider ? . GetFormat ( typeof ( ICustomFormatter ) ) is ICustomFormatter cFormatter )
5176 {
5277 var formatText = format ? . GetLiteralText ( ) ;
53- result = cFormatter . Format ( formatText , current , provider ) ;
54- }
55- // IFormattable
56- // Note: This is what ValueStringBuilder is implementing in the same way
57- else if ( current is IFormattable formattable )
58- {
59- var formatText = format ? . ToString ( ) ;
60- result = formattable . ToString ( formatText , provider ) ;
78+ formattingInfo . Write ( cFormatter . Format ( formatText , current , provider ) . AsSpan ( ) ) ;
79+ return true ;
6180 }
62- else if ( current is string str )
81+
82+ #if NET6_0_OR_GREATER
83+ if ( current is ISpanFormattable spanFormattable )
6384 {
64- formattingInfo . Write ( str . AsSpan ( ) ) ;
85+ // ISpanFormattable has the same speed as IFormattable,
86+ // but brings less GC pressure (e.g. 25% less for processing 1234567.890123f).
87+
88+ var fmtTextSpan = format != null ? format . AsSpan ( ) : Span < char > . Empty ;
89+
90+ // Try to use the stack buffer first
91+ Span < char > buffer = stackalloc char [ StackAllocCharBufferSize ] ;
92+
93+ if ( spanFormattable . TryFormat ( buffer , out var written , fmtTextSpan , provider ) )
94+ {
95+ formattingInfo . Write ( buffer . Slice ( 0 , written ) ) ;
96+ return true ;
97+ }
98+
99+ // If the stack buffer is too small, use a heap buffer
100+ using var arrayBuffer = new ZString . ZCharArray ( 2_000_000 ) ;
101+ arrayBuffer . Write ( spanFormattable , fmtTextSpan , provider ) ;
102+ formattingInfo . Write ( arrayBuffer . GetSpan ( ) ) ;
65103 return true ;
66104 }
67- // ToString:
68- else
105+ #endif
106+
107+ if ( current is IFormattable formattable )
69108 {
70- result = current ? . ToString ( ) ;
109+ var fmtTextString = format ? . ToString ( ) ;
110+ formattingInfo . Write ( formattable . ToString ( fmtTextString , provider ) . AsSpan ( ) ) ;
111+ return true ;
71112 }
72113
73- // Output the result:
74- formattingInfo . Write ( result != null ? result . AsSpan ( ) : ReadOnlySpan < char > . Empty ) ;
75-
114+ // Fallback to ToString (string.ToString() returns 'this')
115+ var result = current != null ? current . ToString ( ) . AsSpan ( ) : Span < char > . Empty ;
116+ formattingInfo . Write ( result ) ;
76117 return true ;
77118 }
78119}
0 commit comments