-
Notifications
You must be signed in to change notification settings - Fork 753
Expand file tree
/
Copy pathLazyStringSplit.cs
More file actions
138 lines (114 loc) · 4.35 KB
/
LazyStringSplit.cs
File metadata and controls
138 lines (114 loc) · 4.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace NuGet.ProjectModel
{
/// <summary>
/// Splits a string by a delimiter, producing substrings lazily during enumeration.
/// Skips empty items, behaving equivalently to <see cref="string.Split(char[])"/> with
/// <see cref="StringSplitOptions.RemoveEmptyEntries"/>.
/// </summary>
/// <remarks>
/// Unlike <see cref="string.Split(char[])"/> and overloads, <see cref="LazyStringSplit"/>
/// does not allocate an array for the return, and allocates strings on demand during
/// enumeration. A custom enumerator type is used so that the only allocations made are
/// the substrings themselves. We also avoid the large internal arrays assigned by the
/// methods on <see cref="string"/>.
/// </remarks>
internal readonly struct LazyStringSplit : IEnumerable<string>
{
private readonly string _input;
private readonly char _delimiter;
public LazyStringSplit(string input, char delimiter)
{
if (input is null)
{
throw new ArgumentNullException(nameof(input));
}
_input = input;
_delimiter = delimiter;
}
public Enumerator GetEnumerator() => new(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
IEnumerator<string> IEnumerable<string>.GetEnumerator() => GetEnumerator();
public IEnumerable<T> Select<T>(Func<string, T> func)
{
foreach (string value in this)
{
yield return func(value);
}
}
public string First()
{
return FirstOrDefault() ?? throw new InvalidOperationException("Sequence is empty.");
}
public string? FirstOrDefault()
{
var enumerator = new Enumerator(this);
return enumerator.MoveNext() ? enumerator.Current : null;
}
public struct Enumerator : IEnumerator<string>
{
private readonly string _input;
private readonly char _delimiter;
private int _index;
internal Enumerator(in LazyStringSplit split)
{
_index = 0;
_input = split._input;
_delimiter = split._delimiter;
Current = null!;
}
public string Current { get; private set; }
public bool MoveNext()
{
while (_index != _input.Length)
{
int delimiterIndex = _input.IndexOf(_delimiter, _index);
if (delimiterIndex == -1)
{
Current = _input.Substring(_index);
_index = _input.Length;
return true;
}
int length = delimiterIndex - _index;
if (length == 0)
{
_index++;
continue;
}
Current = _input.Substring(_index, length);
_index = delimiterIndex + 1;
return true;
}
return false;
}
object IEnumerator.Current => Current;
void IEnumerator.Reset()
{
_index = 0;
Current = null!;
}
void IDisposable.Dispose() { }
}
}
internal static class LazyStringSplitExtensions
{
/// <remarks>
/// This extension method has special knowledge of the <see cref="LazyStringSplit"/> type and
/// can compute its result without allocation.
/// </remarks>
/// <inheritdoc cref="Enumerable.FirstOrDefault{TSource}(IEnumerable{TSource})"/>
public static string? FirstOrDefault(this LazyStringSplit lazyStringSplit)
{
LazyStringSplit.Enumerator enumerator = lazyStringSplit.GetEnumerator();
return enumerator.MoveNext()
? enumerator.Current
: null;
}
}
}