Skip to content

Commit 9910685

Browse files
Added TrimmedEquals and expanded test coverage.
1 parent 9d19b0f commit 9910685

6 files changed

Lines changed: 518 additions & 48 deletions

File tree

Source/Extensions.Equals.cs

Lines changed: 130 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,7 @@ public static bool Equals(this in ReadOnlySpan<char> source, string? other, Stri
2525
};
2626
}
2727

28-
/// <summary>
29-
/// Optimized equals for comparing as span vs a string.
30-
/// </summary>
31-
/// <param name="source">The source span.</param>
32-
/// <param name="other">The string to compare to.</param>
33-
/// <param name="stringComparison">The string comparison type.</param>
34-
/// <returns>True if the are contents equal.</returns>
28+
/// <inheritdoc cref="Equals(in ReadOnlySpan{char}, string?, StringComparison)"/>
3529
public static bool Equals(this in Span<char> source, string? other, StringComparison stringComparison)
3630
{
3731
if (other is null) return false;
@@ -52,7 +46,7 @@ public static bool Equals(this in Span<char> source, string? other, StringCompar
5246
/// <param name="other">The span to compare to.</param>
5347
/// <param name="stringComparison">The string comparison type.</param>
5448
/// <returns>True if the are contents equal.</returns>
55-
public static bool Equals(this in Span<char> source, in Span<char> other, StringComparison stringComparison)
49+
public static bool Equals(this in Span<char> source, in ReadOnlySpan<char> other, StringComparison stringComparison)
5650
{
5751
var len = source.Length;
5852
if (len != other.Length) return false;
@@ -64,14 +58,8 @@ public static bool Equals(this in Span<char> source, in Span<char> other, String
6458
};
6559
}
6660

67-
/// <summary>
68-
/// Optimized equals for comparing spans.
69-
/// </summary>
70-
/// <param name="source">The source span.</param>
71-
/// <param name="other">The span to compare to.</param>
72-
/// <param name="stringComparison">The string comparison type.</param>
73-
/// <returns>True if the are contents equal.</returns>
74-
public static bool Equals(this in Span<char> source, in ReadOnlySpan<char> other, StringComparison stringComparison)
61+
/// <inheritdoc cref="Equals(in Span{char}, in ReadOnlySpan{char}, StringComparison)" />
62+
public static bool Equals(this in Span<char> source, in Span<char> other, StringComparison stringComparison)
7563
{
7664
var len = source.Length;
7765
if (len != other.Length) return false;
@@ -90,10 +78,9 @@ public static bool Equals(this in Span<char> source, in ReadOnlySpan<char> other
9078
/// <param name="other">The span to compare to.</param>
9179
/// <param name="stringComparison">The string comparison type.</param>
9280
/// <returns>True if the are contents equal.</returns>
93-
public static bool Equals(this string source, in ReadOnlySpan<char> other, StringComparison stringComparison)
81+
public static bool Equals(this string? source, in ReadOnlySpan<char> other, StringComparison stringComparison)
9482
{
95-
if (source is null)
96-
throw new ArgumentNullException(nameof(source));
83+
if (source is null) return false;
9784
var len = source.Length;
9885
if (len != other.Length) return false;
9986
return len switch
@@ -104,23 +91,139 @@ public static bool Equals(this string source, in ReadOnlySpan<char> other, Strin
10491
};
10592
}
10693

94+
/// <inheritdoc cref="Equals(string, in ReadOnlySpan{char}, StringComparison)" />
95+
public static bool Equals(this string? source, in Span<char> other, StringComparison stringComparison)
96+
{
97+
if (source is null) return false;
98+
var len = source.Length;
99+
if (len != other.Length) return false;
100+
return len switch
101+
{
102+
0 => true,
103+
1 when stringComparison == StringComparison.Ordinal => source[0] == other[0],
104+
_ => source.AsSpan().Equals(other, stringComparison),
105+
};
106+
}
107+
108+
109+
107110
/// <summary>
108-
/// Optimized equals for comparing as string to a span.
111+
/// Optimized equals for comparing trimmed string with span.
109112
/// </summary>
110113
/// <param name="source">The source string.</param>
111114
/// <param name="other">The span to compare to.</param>
112115
/// <param name="stringComparison">The string comparison type.</param>
113116
/// <returns>True if the are contents equal.</returns>
114-
public static bool Equals(this string source, in Span<char> other, StringComparison stringComparison)
117+
public static bool TrimmedEquals(this string? source, in ReadOnlySpan<char> other, StringComparison stringComparison = StringComparison.Ordinal)
115118
{
116-
if (source is null) throw new ArgumentNullException(nameof(source));
117-
var len = source.Length;
118-
if (len != other.Length) return false;
119-
return len switch
119+
if (source is null) return false;
120+
int slen = source.Length, olen = other.Length;
121+
if (slen < olen) return false;
122+
var span = source.AsSpan().Trim();
123+
slen = span.Length;
124+
if (slen != olen) return false;
125+
return slen switch
120126
{
121127
0 => true,
122-
1 when stringComparison == StringComparison.Ordinal => source[0] == other[0],
123-
_ => source.AsSpan().Equals(other, stringComparison),
128+
1 when stringComparison == StringComparison.Ordinal => span[0] == other[0],
129+
_ => span.Equals(other, stringComparison),
130+
};
131+
}
132+
133+
/// <inheritdoc cref="TrimmedEquals(string, in ReadOnlySpan{char}, StringComparison)"/>
134+
/// <summary>
135+
/// Optimized equals for comparing a trimmed string with another string.
136+
/// </summary>
137+
/// <param name="other">The string to compare to.</param>
138+
public static bool TrimmedEquals(this string? source, string? other, StringComparison stringComparison = StringComparison.Ordinal)
139+
{
140+
if (source is null) return other is null;
141+
if (other is null) return false;
142+
int slen = source.Length, olen = other.Length;
143+
if (slen < olen) return false;
144+
var span = source.AsSpan().Trim();
145+
slen = span.Length;
146+
if (slen != olen) return false;
147+
return slen switch
148+
{
149+
0 => true,
150+
1 when stringComparison == StringComparison.Ordinal => span[0] == other[0],
151+
_ => span.Equals(other.AsSpan(), stringComparison),
152+
};
153+
}
154+
155+
156+
/// <inheritdoc cref="TrimmedEquals(string, in ReadOnlySpan{char}, StringComparison)"/>
157+
/// <param name="trimChar">The character to trim.</param>
158+
public static bool TrimmedEquals(this string? source, in ReadOnlySpan<char> other, char trimChar, StringComparison stringComparison = StringComparison.Ordinal)
159+
{
160+
if (source is null) return false;
161+
int slen = source.Length, olen = other.Length;
162+
if (slen < olen) return false;
163+
var span = source.AsSpan().Trim(trimChar);
164+
slen = span.Length;
165+
if (slen != olen) return false;
166+
return slen switch
167+
{
168+
0 => true,
169+
1 when stringComparison == StringComparison.Ordinal => span[0] == other[0],
170+
_ => span.Equals(other, stringComparison),
171+
};
172+
}
173+
174+
/// <inheritdoc cref="TrimmedEquals(string?, string?, StringComparison)"/>
175+
/// <param name="trimChar">The character to trim.</param>
176+
public static bool TrimmedEquals(this string? source, string? other, char trimChar, StringComparison stringComparison = StringComparison.Ordinal)
177+
{
178+
if (source is null) return other is null;
179+
if (other is null) return false;
180+
int slen = source.Length, olen = other.Length;
181+
if (slen < olen) return false;
182+
var span = source.AsSpan().Trim(trimChar);
183+
slen = span.Length;
184+
if (slen != olen) return false;
185+
return slen switch
186+
{
187+
0 => true,
188+
1 when stringComparison == StringComparison.Ordinal => span[0] == other[0],
189+
_ => span.Equals(other.AsSpan(), stringComparison),
190+
};
191+
}
192+
193+
/// <inheritdoc cref="TrimmedEquals(string, in ReadOnlySpan{char}, StringComparison)"/>
194+
/// <param name="trimChars">The characters to trim.</param>
195+
public static bool TrimmedEquals(this string? source, in ReadOnlySpan<char> other, in ReadOnlySpan<char> trimChars, StringComparison stringComparison = StringComparison.Ordinal)
196+
{
197+
if (source is null) return false;
198+
int slen = source.Length, olen = other.Length;
199+
if (slen < olen) return false;
200+
var span = source.AsSpan().Trim(trimChars);
201+
slen = span.Length;
202+
if (slen != olen) return false;
203+
return slen switch
204+
{
205+
0 => true,
206+
1 when stringComparison == StringComparison.Ordinal => span[0] == other[0],
207+
_ => span.Equals(other, stringComparison),
208+
};
209+
}
210+
211+
/// <inheritdoc cref="TrimmedEquals(string?, string?, StringComparison)"/>
212+
/// <param name="trimChars">The characters to trim.</param>
213+
public static bool TrimmedEquals(this string? source, string? other, in ReadOnlySpan<char> trimChars, StringComparison stringComparison = StringComparison.Ordinal)
214+
{
215+
if (source is null) return other is null;
216+
if (other is null) return false;
217+
int slen = source.Length, olen = other.Length;
218+
if (slen < olen) return false;
219+
var span = source.AsSpan().Trim(trimChars);
220+
slen = span.Length;
221+
if (slen != olen) return false;
222+
return slen switch
223+
{
224+
0 => true,
225+
1 when stringComparison == StringComparison.Ordinal => span[0] == other[0],
226+
_ => span.Equals(other.AsSpan(), stringComparison),
124227
};
125228
}
126229
}

Source/StringBuilderExtensions.cs

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ public static StringBuilder ToStringBuilder<T>(this in ReadOnlySpan<T> source)
2929
return sb;
3030
}
3131

32+
/// <inheritdoc cref="ToStringBuilder{T}(in ReadOnlySpan{T})">
33+
public static StringBuilder ToStringBuilder<T>(this in Span<T> source)
34+
{
35+
var len = source.Length;
36+
var sb = new StringBuilder(len);
37+
38+
for (var i = 0; i < len; i++)
39+
sb.Append(source[i]);
40+
41+
return sb;
42+
}
43+
3244
/// <summary>
3345
/// Adds every entry to a StringBuilder.
3446
/// </summary>
@@ -52,7 +64,27 @@ public static StringBuilder ToStringBuilder<T>(this IEnumerable<T> source)
5264
/// <param name="source">The source span.</param>
5365
/// <param name="separator">The separator sequence.</param>
5466
/// <returns>The resultant StringBuilder.</returns>
55-
public static StringBuilder ToStringBuilder<T>(this in ReadOnlySpan<T> source, string separator)
67+
public static StringBuilder ToStringBuilder<T>(this in ReadOnlySpan<T> source, string? separator)
68+
{
69+
var len = source.Length;
70+
if (len < 2 || string.IsNullOrEmpty(separator))
71+
return ToStringBuilder(source);
72+
73+
var sb = new StringBuilder(2 * len - 1);
74+
75+
sb.Append(source[0]);
76+
for (var i = 1; i < len; i++)
77+
{
78+
sb.Append(separator);
79+
sb.Append(source[i]);
80+
}
81+
82+
return sb;
83+
}
84+
85+
86+
/// <inheritdoc cref="ToStringBuilder{T}(in ReadOnlySpan{T}, string)">
87+
public static StringBuilder ToStringBuilder<T>(this in Span<T> source, string? separator)
5688
{
5789
var len = source.Length;
5890
if (len < 2 || string.IsNullOrEmpty(separator))
@@ -94,14 +126,32 @@ public static StringBuilder ToStringBuilder<T>(this in ReadOnlySpan<T> source, c
94126
return sb;
95127
}
96128

129+
/// <inheritdoc cref="ToStringBuilder{T}(in ReadOnlySpan{T}, char)">
130+
public static StringBuilder ToStringBuilder<T>(this in Span<T> source, char separator)
131+
{
132+
var len = source.Length;
133+
if (len < 2) return ToStringBuilder(source);
134+
135+
var sb = new StringBuilder(2 * len - 1);
136+
137+
sb.Append(source[0]);
138+
for (var i = 1; i < len; i++)
139+
{
140+
sb.Append(separator);
141+
sb.Append(source[i]);
142+
}
143+
144+
return sb;
145+
}
146+
97147
/// <summary>
98148
/// Adds every entry to a StringBuilder separated by the specified sequence.
99149
/// </summary>
100150
/// <typeparam name="T">The type of the source.</typeparam>
101151
/// <param name="source">The source enumerable.</param>
102152
/// <param name="separator">The separator sequence.</param>
103153
/// <returns>The resultant StringBuilder.</returns>
104-
public static StringBuilder ToStringBuilder<T>(this IEnumerable<T> source, string separator)
154+
public static StringBuilder ToStringBuilder<T>(this IEnumerable<T> source, string? separator)
105155
{
106156
if (source is null) throw new ArgumentNullException(nameof(source));
107157
Contract.EndContractBlock();
@@ -127,7 +177,7 @@ public static StringBuilder ToStringBuilder<T>(this IEnumerable<T> source, char
127177
/// <summary>
128178
/// Shortcut for adding an array of values to a StringBuilder.
129179
/// </summary>
130-
public static StringBuilder AppendAll<T>(this StringBuilder target, IEnumerable<T> values)
180+
public static StringBuilder AppendAll<T>(this StringBuilder target, IEnumerable<T>? values)
131181
{
132182
if (target is null)
133183
throw new ArgumentNullException(nameof(values));
@@ -142,7 +192,7 @@ public static StringBuilder AppendAll<T>(this StringBuilder target, IEnumerable<
142192
/// <summary>
143193
/// Shortcut for adding an array of values to a StringBuilder.
144194
/// </summary>
145-
public static StringBuilder AppendAll<T>(this StringBuilder target, IEnumerable<T> values, string separator)
195+
public static StringBuilder AppendAll<T>(this StringBuilder target, IEnumerable<T>? values, string? separator)
146196
{
147197
if (target is null)
148198
throw new ArgumentNullException(nameof(values));
@@ -165,7 +215,7 @@ public static StringBuilder AppendAll<T>(this StringBuilder target, IEnumerable<
165215
/// <summary>
166216
/// Shortcut for adding an array of values to a StringBuilder.
167217
/// </summary>
168-
public static StringBuilder AppendAll<T>(this StringBuilder target, IEnumerable<T> values, char separator)
218+
public static StringBuilder AppendAll<T>(this StringBuilder target, IEnumerable<T>? values, char separator)
169219
{
170220
if (target is null)
171221
throw new ArgumentNullException(nameof(values));
@@ -191,7 +241,6 @@ public static StringBuilder AppendAll<T>(this StringBuilder target, in ReadOnlyS
191241
throw new ArgumentNullException(nameof(values));
192242
Contract.EndContractBlock();
193243

194-
if (values == null) return target;
195244
foreach (var value in values)
196245
target.Append(value);
197246
return target;
@@ -200,14 +249,12 @@ public static StringBuilder AppendAll<T>(this StringBuilder target, in ReadOnlyS
200249
/// <summary>
201250
/// Shortcut for adding an array of values to a StringBuilder.
202251
/// </summary>
203-
public static StringBuilder AppendAll<T>(this StringBuilder target, in ReadOnlySpan<T> values, string separator)
252+
public static StringBuilder AppendAll<T>(this StringBuilder target, in ReadOnlySpan<T> values, string? separator)
204253
{
205254
if (target is null)
206255
throw new ArgumentNullException(nameof(values));
207256
Contract.EndContractBlock();
208257

209-
if (values == null) return target;
210-
211258
if (string.IsNullOrEmpty(separator))
212259
return target.AppendAll(values);
213260

@@ -229,8 +276,6 @@ public static StringBuilder AppendAll<T>(this StringBuilder target, in ReadOnlyS
229276
throw new ArgumentNullException(nameof(values));
230277
Contract.EndContractBlock();
231278

232-
if (values == null) return target;
233-
234279
var e = values.GetEnumerator();
235280
if (!e.MoveNext()) return target;
236281
if (target.Length != 0) target.Append(separator);
@@ -243,7 +288,7 @@ public static StringBuilder AppendAll<T>(this StringBuilder target, in ReadOnlyS
243288
/// <summary>
244289
/// Appends values to StringBuilder prefixing the provided separator.
245290
/// </summary>
246-
public static StringBuilder AppendWithSeparator<T>(this StringBuilder target, string separator, T value, params T[] values)
291+
public static StringBuilder AppendWithSeparator(this StringBuilder target, string? separator, object value, params object[] values)
247292
{
248293
if (target is null)
249294
throw new ArgumentNullException(nameof(values));
@@ -271,7 +316,7 @@ public static StringBuilder AppendWithSeparator<T>(this StringBuilder target, st
271316
/// <summary>
272317
/// Appends values to StringBuilder prefixing the provided separator.
273318
/// </summary>
274-
public static StringBuilder AppendWithSeparator<T>(this StringBuilder target, char separator, T value, params T[] values)
319+
public static StringBuilder AppendWithSeparator(this StringBuilder target, char separator, object value, params object[] values)
275320
{
276321
if (target is null)
277322
throw new ArgumentNullException(nameof(values));
@@ -281,7 +326,7 @@ public static StringBuilder AppendWithSeparator<T>(this StringBuilder target, ch
281326
target.Append(separator);
282327
target.Append(value);
283328
foreach (var v in values)
284-
target.AppendWithSeparator(separator, v);
329+
target.Append(separator).Append(v);
285330

286331
return target;
287332
}

0 commit comments

Comments
 (0)