@@ -92,53 +92,45 @@ public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char
92
92
ArgumentOutOfRangeException . ThrowIfLessThan ( startIndex , 0 ) ;
93
93
ArgumentOutOfRangeException . ThrowIfGreaterThan ( startIndex + count , Length ) ;
94
94
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 ) )
99
96
{
100
97
return ;
101
98
}
102
99
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 ;
108
102
109
- if ( hits . IsEmpty )
103
+ while ( remainingChars > 0 )
110
104
{
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
+ }
115
110
116
- for ( var i = 0 ; i < hits . Length ; i ++ )
117
- {
118
- var index = startIndex + hits [ i ] + ( delta * i ) ;
111
+ index += foundSubIndex ;
112
+ remainingChars -= foundSubIndex ;
119
113
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 )
123
115
{
116
+ // Just replace the old slice
124
117
newValue . CopyTo ( buffer [ index ..] ) ;
125
- Remove ( index + newValue . Length , - delta ) ;
126
118
}
127
-
128
- // Same length -> We can just replace the memory slice
129
- else if ( delta == 0 )
119
+ else if ( newValue . Length < oldValue . Length )
130
120
{
121
+ // Replace the old slice and trim the unused slice
131
122
newValue . CopyTo ( buffer [ index ..] ) ;
123
+ Remove ( index + newValue . Length , oldValue . Length - newValue . Length ) ;
132
124
}
133
-
134
- // newValue is larger than the old value
135
- // First add until the old memory region
136
- // and insert afterwards the rest
137
125
else
138
126
{
127
+ // Replace the old slice and append the extra slice
139
128
newValue [ ..oldValue . Length ] . CopyTo ( buffer [ index ..] ) ;
140
129
Insert ( index + oldValue . Length , newValue [ oldValue . Length ..] ) ;
141
130
}
131
+
132
+ index += newValue . Length ;
133
+ remainingChars -= oldValue . Length ;
142
134
}
143
135
}
144
136
@@ -153,7 +145,7 @@ public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char
153
145
/// </remarks>
154
146
/// /// <typeparam name="T">Any type.</typeparam>
155
147
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
156
- public void ReplaceGeneric < T > ( ReadOnlySpan < char > oldValue , T newValue )
148
+ public void ReplaceGeneric < T > ( scoped ReadOnlySpan < char > oldValue , T newValue )
157
149
=> ReplaceGeneric ( oldValue , newValue , 0 , Length ) ;
158
150
159
151
/// <summary>
@@ -164,24 +156,23 @@ public void ReplaceGeneric<T>(ReadOnlySpan<char> oldValue, T newValue)
164
156
/// <param name="startIndex">The index to start in this builder.</param>
165
157
/// <param name="count">The number of characters to read in this builder.</param>
166
158
/// <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 .
169
161
/// </remarks>
170
162
/// /// <typeparam name="T">Any type.</typeparam>
171
163
[ 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 )
173
165
{
174
166
if ( newValue is ISpanFormattable spanFormattable )
175
167
{
176
- Span < char > tempBuffer = stackalloc char [ 24 ] ;
168
+ Span < char > tempBuffer = stackalloc char [ 128 ] ;
177
169
if ( spanFormattable . TryFormat ( tempBuffer , out var written , default , null ) )
178
170
{
179
171
Replace ( oldValue , tempBuffer [ ..written ] , startIndex , count ) ;
172
+ return ;
180
173
}
181
174
}
182
- else
183
- {
184
- Replace ( oldValue , ( newValue ? . ToString ( ) ?? string . Empty ) . AsSpan ( ) , startIndex , count ) ;
185
- }
175
+
176
+ Replace ( oldValue , newValue ? . ToString ( ) ?? string . Empty , startIndex , count ) ;
186
177
}
187
178
}
0 commit comments