Skip to content

Commit 8d4aa86

Browse files
Refactor string splitting methods and update dependencies
Refactored string splitting methods to simplify and optimize implementation, removing redundant internal methods and incorporating segment-based approaches. Added conditional trimming based on StringSplitOptions. Updated project version to 8.2.0 and upgraded Microsoft.SourceLink.GitHub package. Updated Open.Text.Tests.csproj to use newer versions of several NuGet packages. Added new test cases in SplitTests.cs for additional scenarios and modified existing test logic. Removed SplitOptionsInvalid test method.
1 parent df0bd55 commit 8d4aa86

6 files changed

Lines changed: 293 additions & 312 deletions

File tree

Source/Extensions.Split.cs

Lines changed: 86 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,6 @@ private static ReadOnlySpan<char> FirstSplitSpan(ReadOnlySpan<char> rest, int i,
3636
: rest.Slice(0, i);
3737
}
3838

39-
private static ReadOnlyMemory<char> FirstSplitMemory(string source, int start, int i, int n, out int nextIndex)
40-
{
41-
Debug.Assert(start >= 0);
42-
if (i == -1)
43-
{
44-
nextIndex = -1;
45-
return start == 0 ? source.AsMemory() : source.AsMemory(start);
46-
}
47-
48-
nextIndex = i + n;
49-
int segmentLen = i - start;
50-
return segmentLen == 0
51-
? ReadOnlyMemory<char>.Empty
52-
: source.AsMemory(start, segmentLen);
53-
}
54-
5539
/// <summary>
5640
/// Finds the first instance of a character and returns the set of characters up to that character.
5741
/// </summary>
@@ -177,46 +161,17 @@ public static ReadOnlySpan<char> FirstSplit(this ReadOnlySpan<char> source,
177161
/// <param name="splitCharacter">The character to find.</param>
178162
/// <param name="options">Can specify to omit empty entries.</param>
179163
/// <returns>An enumerable of the segments.</returns>
180-
public static IEnumerable<string> SplitToEnumerable(this string source,
181-
char splitCharacter,
182-
StringSplitOptions options = StringSplitOptions.None)
164+
public static IEnumerable<string> SplitToEnumerable(
165+
this string source,
166+
char splitCharacter,
167+
StringSplitOptions options = StringSplitOptions.None)
183168
{
184169
if (source is null) throw new ArgumentNullException(nameof(source));
185-
Contract.EndContractBlock();
186170

187-
return options switch
188-
{
189-
StringSplitOptions.None => source.Length == 0
190-
? Enumerable.Repeat(string.Empty, 1)
191-
: SplitAsEnumerableCore(),
192-
StringSplitOptions.RemoveEmptyEntries => source.Length == 0
193-
? Enumerable.Empty<string>()
194-
: SplitAsEnumerableCoreOmitEmpty(),
195-
_ => throw new System.ComponentModel.InvalidEnumArgumentException(),
196-
};
197-
198-
IEnumerable<string> SplitAsEnumerableCore()
199-
{
200-
int startIndex = 0;
201-
do
202-
{
203-
yield return source.FirstSplit(splitCharacter, out int nextIndex, startIndex).ToString();
204-
startIndex = nextIndex;
205-
}
206-
while (startIndex != -1);
207-
}
208-
209-
IEnumerable<string> SplitAsEnumerableCoreOmitEmpty()
210-
{
211-
int startIndex = 0;
212-
do
213-
{
214-
ReadOnlySpan<char> result = source.FirstSplit(splitCharacter, out int nextIndex, startIndex);
215-
if (result.Length != 0) yield return result.ToString();
216-
startIndex = nextIndex;
217-
}
218-
while (startIndex != -1);
219-
}
171+
Contract.EndContractBlock();
172+
return source.AsSegment()
173+
.SplitAsSegments(splitCharacter, options)
174+
.Select(s => s.ToString());
220175
}
221176

222177
/// <summary>
@@ -229,49 +184,16 @@ IEnumerable<string> SplitAsEnumerableCoreOmitEmpty()
229184
/// <returns>An IEnumerable&lt;string&gt; of the segments.</returns>
230185
public static IEnumerable<string> SplitToEnumerable(
231186
this string source,
232-
string splitSequence,
187+
StringSegment splitSequence,
233188
StringSplitOptions options = StringSplitOptions.None,
234189
StringComparison comparisonType = StringComparison.Ordinal)
235190
{
236191
if (source is null) throw new ArgumentNullException(nameof(source));
237-
if (splitSequence is null) throw new ArgumentNullException(nameof(splitSequence));
238-
if (splitSequence.Length == 0)
239-
throw new ArgumentException("Cannot split using empty sequence.", nameof(splitSequence));
240192
Contract.EndContractBlock();
241193

242-
return options switch
243-
{
244-
StringSplitOptions.None => source.Length == 0
245-
? Enumerable.Repeat(string.Empty, 1)
246-
: SplitAsEnumerableCore(source, splitSequence, comparisonType),
247-
StringSplitOptions.RemoveEmptyEntries => source.Length == 0
248-
? Enumerable.Empty<string>()
249-
: SplitAsEnumerableCoreOmitEmpty(source, splitSequence, comparisonType),
250-
_ => throw new System.ComponentModel.InvalidEnumArgumentException(),
251-
};
252-
253-
static IEnumerable<string> SplitAsEnumerableCore(string source, string splitSequence, StringComparison comparisonType)
254-
{
255-
int startIndex = 0;
256-
do
257-
{
258-
yield return source.FirstSplit(splitSequence, out int nextIndex, startIndex, comparisonType).ToString();
259-
startIndex = nextIndex;
260-
}
261-
while (startIndex != -1);
262-
}
263-
264-
static IEnumerable<string> SplitAsEnumerableCoreOmitEmpty(string source, string splitSequence, StringComparison comparisonType)
265-
{
266-
int startIndex = 0;
267-
do
268-
{
269-
ReadOnlySpan<char> result = source.FirstSplit(splitSequence, out int nextIndex, startIndex, comparisonType);
270-
if (result.Length != 0) yield return result.ToString();
271-
startIndex = nextIndex;
272-
}
273-
while (startIndex != -1);
274-
}
194+
return source.AsSegment()
195+
.SplitAsSegments(splitSequence, options, comparisonType)
196+
.Select(s => s.ToString());
275197
}
276198

277199
/// <returns>An IEnumerable&lt;ReadOnlyMemory&lt;char&gt;&gt; of the segments.</returns>
@@ -280,94 +202,18 @@ public static IEnumerable<ReadOnlyMemory<char>> SplitAsMemory(
280202
this string source,
281203
char splitCharacter,
282204
StringSplitOptions options = StringSplitOptions.None)
283-
{
284-
if (source is null) throw new ArgumentNullException(nameof(source));
285-
Contract.EndContractBlock();
286-
287-
return options switch
288-
{
289-
StringSplitOptions.None => source.Length == 0
290-
? Enumerable.Repeat(ReadOnlyMemory<char>.Empty, 1)
291-
: SplitAsMemoryCore(source, splitCharacter),
292-
StringSplitOptions.RemoveEmptyEntries => source.Length == 0
293-
? Enumerable.Empty<ReadOnlyMemory<char>>()
294-
: SplitAsMemoryOmitEmpty(source, splitCharacter),
295-
_ => throw new System.ComponentModel.InvalidEnumArgumentException(),
296-
};
297-
298-
static IEnumerable<ReadOnlyMemory<char>> SplitAsMemoryCore(string source, char splitCharacter)
299-
{
300-
int startIndex = 0;
301-
do
302-
{
303-
yield return FirstSplitMemory(source, startIndex, source.IndexOf(splitCharacter, startIndex), 1, out int nextIndex);
304-
startIndex = nextIndex;
305-
}
306-
while (startIndex != -1);
307-
}
308-
309-
static IEnumerable<ReadOnlyMemory<char>> SplitAsMemoryOmitEmpty(string source, char splitCharacter)
310-
{
311-
int startIndex = 0;
312-
do
313-
{
314-
ReadOnlyMemory<char> result = FirstSplitMemory(source, startIndex, source.IndexOf(splitCharacter, startIndex), 1, out int nextIndex);
315-
if (result.Length != 0) yield return result;
316-
startIndex = nextIndex;
317-
}
318-
while (startIndex != -1);
319-
}
320-
}
205+
=> SplitAsSegments(source.AsSegment(), splitCharacter, options)
206+
.Select(s => s.AsMemory());
321207

322208
/// <returns>An IEnumerable&lt;ReadOnlyMemory&lt;char&gt;&gt; of the segments.</returns>
323-
/// <inheritdoc cref="SplitToEnumerable(string, string, StringSplitOptions, StringComparison)"/>
209+
/// <inheritdoc cref="SplitToEnumerable(string, StringSegment, StringSplitOptions, StringComparison)"/>
324210
public static IEnumerable<ReadOnlyMemory<char>> SplitAsMemory(this string source,
325211
string splitSequence,
326212
StringSplitOptions options = StringSplitOptions.None,
327213
StringComparison comparisonType = StringComparison.Ordinal)
328-
{
329-
if (source is null) throw new ArgumentNullException(nameof(source));
330-
if (splitSequence is null) throw new ArgumentNullException(nameof(splitSequence));
331-
if (splitSequence.Length == 0)
332-
throw new ArgumentException("Cannot split using empty sequence.", nameof(splitSequence));
333-
Contract.EndContractBlock();
334214

335-
return options switch
336-
{
337-
StringSplitOptions.None => source.Length == 0
338-
? Enumerable.Repeat(ReadOnlyMemory<char>.Empty, 1)
339-
: SplitAsMemoryCore(source, splitSequence, comparisonType),
340-
StringSplitOptions.RemoveEmptyEntries => source.Length == 0
341-
? Enumerable.Empty<ReadOnlyMemory<char>>()
342-
: SplitAsMemoryOmitEmpty(source, splitSequence, comparisonType),
343-
_ => throw new System.ComponentModel.InvalidEnumArgumentException(),
344-
};
345-
346-
static IEnumerable<ReadOnlyMemory<char>> SplitAsMemoryOmitEmpty(string source, string splitSequence, StringComparison comparisonType)
347-
{
348-
int startIndex = 0;
349-
int splitLen = splitSequence.Length;
350-
do
351-
{
352-
ReadOnlyMemory<char> result = FirstSplitMemory(source, startIndex, source.IndexOf(splitSequence, startIndex, comparisonType), splitLen, out int nextIndex);
353-
if (result.Length != 0) yield return result;
354-
startIndex = nextIndex;
355-
}
356-
while (startIndex != -1);
357-
}
358-
359-
static IEnumerable<ReadOnlyMemory<char>> SplitAsMemoryCore(string source, string splitSequence, StringComparison comparisonType)
360-
{
361-
int startIndex = 0;
362-
int splitLen = splitSequence.Length;
363-
do
364-
{
365-
yield return FirstSplitMemory(source, startIndex, source.IndexOf(splitSequence, startIndex, comparisonType), splitLen, out int nextIndex);
366-
startIndex = nextIndex;
367-
}
368-
while (startIndex != -1);
369-
}
370-
}
215+
=> SplitAsSegments(source.AsSegment(), splitSequence, options, comparisonType)
216+
.Select(s => s.AsMemory());
371217

372218
/// <summary>
373219
/// Splits a sequence of characters into strings using the character provided.
@@ -376,42 +222,46 @@ static IEnumerable<ReadOnlyMemory<char>> SplitAsMemoryCore(string source, string
376222
/// <param name="splitCharacter">The character to split by.</param>
377223
/// <param name="options">Can specify to omit empty entries.</param>
378224
/// <returns>The resultant list of string segments.</returns>
379-
public static IReadOnlyList<string> Split(this ReadOnlySpan<char> source,
225+
public static IReadOnlyList<string> Split(
226+
this ReadOnlySpan<char> source,
380227
char splitCharacter,
381228
StringSplitOptions options = StringSplitOptions.None)
382229
{
383-
switch (options)
230+
if (source.IsEmpty)
384231
{
385-
case StringSplitOptions.None when source.Length == 0:
386-
return SingleEmpty.Instance;
387-
388-
case StringSplitOptions.RemoveEmptyEntries when source.Length == 0:
389-
return Array.Empty<string>();
390-
391-
case StringSplitOptions.RemoveEmptyEntries:
392-
{
393-
Debug.Assert(!source.IsEmpty);
394-
var list = new List<string>();
395-
396-
loop:
397-
ReadOnlySpan<char> result = source.FirstSplit(splitCharacter, out int nextIndex);
398-
if (!result.IsEmpty) list.Add(result.ToString());
399-
if (nextIndex == -1) return list;
400-
source = source.Slice(nextIndex);
401-
goto loop;
402-
}
403-
404-
default:
405-
{
406-
Debug.Assert(!source.IsEmpty);
407-
var list = new List<string>();
408-
loop:
409-
ReadOnlySpan<char> result = source.FirstSplit(splitCharacter, out int nextIndex);
410-
list.Add(result.IsEmpty ? string.Empty : result.ToString());
411-
if (nextIndex == -1) return list;
412-
source = source.Slice(nextIndex);
413-
goto loop;
414-
}
232+
return options.HasFlag(StringSplitOptions.RemoveEmptyEntries)
233+
? Array.Empty<string>()
234+
: SingleEmpty.Instance;
235+
}
236+
237+
var list = new List<string>();
238+
239+
#if NET5_0_OR_GREATER
240+
bool trimEach = options.HasFlag(StringSplitOptions.TrimEntries);
241+
#endif
242+
if (options.HasFlag(StringSplitOptions.RemoveEmptyEntries))
243+
{
244+
loop:
245+
ReadOnlySpan<char> result = source.FirstSplit(splitCharacter, out int nextIndex);
246+
#if NET5_0_OR_GREATER
247+
if (trimEach) result = result.Trim();
248+
#endif
249+
if (!result.IsEmpty) list.Add(result.ToString());
250+
if (nextIndex == -1) return list;
251+
source = source.Slice(nextIndex);
252+
goto loop;
253+
}
254+
255+
{
256+
loop:
257+
ReadOnlySpan<char> result = source.FirstSplit(splitCharacter, out int nextIndex);
258+
#if NET5_0_OR_GREATER
259+
if (trimEach) result = result.Trim();
260+
#endif
261+
list.Add(result.IsEmpty ? string.Empty : result.ToString());
262+
if (nextIndex == -1) return list;
263+
source = source.Slice(nextIndex);
264+
goto loop;
415265
}
416266
}
417267

@@ -428,38 +278,40 @@ public static IReadOnlyList<string> Split(this ReadOnlySpan<char> source,
428278
StringSplitOptions options = StringSplitOptions.None,
429279
StringComparison comparisonType = StringComparison.Ordinal)
430280
{
431-
switch (options)
281+
if (source.IsEmpty)
282+
{
283+
return options.HasFlag(StringSplitOptions.RemoveEmptyEntries)
284+
? Array.Empty<string>()
285+
: SingleEmpty.Instance;
286+
}
287+
288+
var list = new List<string>();
289+
#if NET5_0_OR_GREATER
290+
bool trimEach = options.HasFlag(StringSplitOptions.TrimEntries);
291+
#endif
292+
if (options.HasFlag(StringSplitOptions.RemoveEmptyEntries))
293+
{
294+
loop:
295+
ReadOnlySpan<char> result = source.FirstSplit(splitSequence, out int nextIndex, comparisonType);
296+
#if NET5_0_OR_GREATER
297+
if (trimEach) result = result.Trim();
298+
#endif
299+
if (!result.IsEmpty) list.Add(result.ToString());
300+
if (nextIndex == -1) return list;
301+
source = source.Slice(nextIndex);
302+
goto loop;
303+
}
304+
432305
{
433-
case StringSplitOptions.None when source.IsEmpty:
434-
return SingleEmpty.Instance;
435-
436-
case StringSplitOptions.RemoveEmptyEntries when source.IsEmpty:
437-
return Array.Empty<string>();
438-
439-
case StringSplitOptions.RemoveEmptyEntries:
440-
{
441-
Debug.Assert(!source.IsEmpty);
442-
var list = new List<string>();
443-
444-
loop:
445-
ReadOnlySpan<char> result = source.FirstSplit(splitSequence, out int nextIndex, comparisonType);
446-
if (!result.IsEmpty) list.Add(result.ToString());
447-
if (nextIndex == -1) return list;
448-
source = source.Slice(nextIndex);
449-
goto loop;
450-
}
451-
452-
default:
453-
{
454-
Debug.Assert(!source.IsEmpty);
455-
var list = new List<string>();
456-
loop:
457-
ReadOnlySpan<char> result = source.FirstSplit(splitSequence, out int nextIndex, comparisonType);
458-
list.Add(result.IsEmpty ? string.Empty : result.ToString());
459-
if (nextIndex == -1) return list;
460-
source = source.Slice(nextIndex);
461-
goto loop;
462-
}
306+
loop:
307+
ReadOnlySpan<char> result = source.FirstSplit(splitSequence, out int nextIndex, comparisonType);
308+
#if NET5_0_OR_GREATER
309+
if (trimEach) result = result.Trim();
310+
#endif
311+
list.Add(result.IsEmpty ? string.Empty : result.ToString());
312+
if (nextIndex == -1) return list;
313+
source = source.Slice(nextIndex);
314+
goto loop;
463315
}
464316
}
465317

0 commit comments

Comments
 (0)