@@ -92,53 +92,45 @@ public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char
9292 ArgumentOutOfRangeException . ThrowIfLessThan ( startIndex , 0 ) ;
9393 ArgumentOutOfRangeException . ThrowIfGreaterThan ( startIndex + count , Length ) ;
9494
95- var length = startIndex + count ;
96- var slice = buffer [ startIndex ..length ] ;
97-
98- if ( oldValue . SequenceEqual ( newValue ) )
95+ if ( oldValue . IsEmpty || oldValue . Equals ( newValue , StringComparison . Ordinal ) )
9996 {
10097 return ;
10198 }
10299
103- // We might want to check whether or not we want to introduce different
104- // string search algorithms for longer strings.
105- // I had checked initially with Boyer-Moore but it didn't make that much sense as we
106- // don't expect very long strings and then the performance is literally the same. So I went with the easier solution.
107- var hits = NaiveSearch . FindAll ( slice , oldValue ) ;
100+ var index = startIndex ;
101+ var remainingChars = count ;
108102
109- if ( hits . IsEmpty )
103+ while ( remainingChars > 0 )
110104 {
111- return ;
112- }
113-
114- var delta = newValue . Length - oldValue . Length ;
105+ var foundSubIndex = buffer . Slice ( index , remainingChars ) . IndexOf ( oldValue , StringComparison . Ordinal ) ;
106+ if ( foundSubIndex < 0 )
107+ {
108+ break ;
109+ }
115110
116- for ( var i = 0 ; i < hits . Length ; i ++ )
117- {
118- var index = startIndex + hits [ i ] + ( delta * i ) ;
111+ index += foundSubIndex ;
112+ remainingChars -= foundSubIndex ;
119113
120- // newValue is smaller than old value
121- // We can insert the slice and remove the overhead
122- if ( delta < 0 )
114+ if ( newValue . Length == oldValue . Length )
123115 {
116+ // Just replace the old slice
124117 newValue . CopyTo ( buffer [ index ..] ) ;
125- Remove ( index + newValue . Length , - delta ) ;
126118 }
127-
128- // Same length -> We can just replace the memory slice
129- else if ( delta == 0 )
119+ else if ( newValue . Length < oldValue . Length )
130120 {
121+ // Replace the old slice and trim the unused slice
131122 newValue . CopyTo ( buffer [ index ..] ) ;
123+ Remove ( index + newValue . Length , oldValue . Length - newValue . Length ) ;
132124 }
133-
134- // newValue is larger than the old value
135- // First add until the old memory region
136- // and insert afterwards the rest
137125 else
138126 {
127+ // Replace the old slice and append the extra slice
139128 newValue [ ..oldValue . Length ] . CopyTo ( buffer [ index ..] ) ;
140129 Insert ( index + oldValue . Length , newValue [ oldValue . Length ..] ) ;
141130 }
131+
132+ index += newValue . Length ;
133+ remainingChars -= oldValue . Length ;
142134 }
143135 }
144136
@@ -153,7 +145,7 @@ public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char
153145 /// </remarks>
154146 /// /// <typeparam name="T">Any type.</typeparam>
155147 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
156- public void ReplaceGeneric < T > ( ReadOnlySpan < char > oldValue , T newValue )
148+ public void ReplaceGeneric < T > ( scoped ReadOnlySpan < char > oldValue , T newValue )
157149 => ReplaceGeneric ( oldValue , newValue , 0 , Length ) ;
158150
159151 /// <summary>
@@ -164,24 +156,23 @@ public void ReplaceGeneric<T>(ReadOnlySpan<char> oldValue, T newValue)
164156 /// <param name="startIndex">The index to start in this builder.</param>
165157 /// <param name="count">The number of characters to read in this builder.</param>
166158 /// <remarks>
167- /// If <paramref name="newValue"/> is from type <see cref="ISpanFormattable"/> an optimized version is taken .
168- /// Otherwise the ToString method is called .
159+ /// If <paramref name="newValue"/> is <see cref="ISpanFormattable"/>, <c>TryFormat</c> is used .
160+ /// Otherwise, <c> ToString</c> is used .
169161 /// </remarks>
170162 /// /// <typeparam name="T">Any type.</typeparam>
171163 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
172- public void ReplaceGeneric < T > ( ReadOnlySpan < char > oldValue , T newValue , int startIndex , int count )
164+ public void ReplaceGeneric < T > ( scoped ReadOnlySpan < char > oldValue , T newValue , int startIndex , int count )
173165 {
174166 if ( newValue is ISpanFormattable spanFormattable )
175167 {
176- Span < char > tempBuffer = stackalloc char [ 24 ] ;
168+ Span < char > tempBuffer = stackalloc char [ 128 ] ;
177169 if ( spanFormattable . TryFormat ( tempBuffer , out var written , default , null ) )
178170 {
179171 Replace ( oldValue , tempBuffer [ ..written ] , startIndex , count ) ;
172+ return ;
180173 }
181174 }
182- else
183- {
184- Replace ( oldValue , ( newValue ? . ToString ( ) ?? string . Empty ) . AsSpan ( ) , startIndex , count ) ;
185- }
175+
176+ Replace ( oldValue , newValue ? . ToString ( ) ?? string . Empty , startIndex , count ) ;
186177 }
187178}
0 commit comments