Skip to content

Commit 230c639

Browse files
The most significant changes include the addition of new classes and structures to handle character sequences and their enumeration. The CharacterSequence struct, StringBuilderEnumerator struct, StringConcat class, and StringSegmentEnumerator struct have been added to represent and manipulate sequences of characters. Additionally, the Equals method in both StringComparable.cs and StringSubsegment.cs has been updated to allow for nullable objects.
Here is a list of the changes: 1. The `Equals` method in `StringComparable.cs` has been updated to allow for nullable objects. This change enhances the method's flexibility in handling different types of inputs. 2. The `GetHashCode` method in `StringComparable.cs` has been updated to handle different .NET Standard versions. This ensures the method's compatibility across different .NET Standard versions. 3. The `StringSegmentComparer` class in `StringSegmentComparer.cs` has been updated to include additional parameters in the constructor and their corresponding properties. This allows for more detailed object instantiation. 4. The `Equals` method in `StringSubsegment.cs` has been updated to allow for nullable objects. This enhances the method's flexibility in handling different types of inputs. 5. A new `CharacterSequence` struct has been added in `CharacterSequence.cs` to represent a sequence of characters. This provides a new way to handle character sequences. 6. A new `StringBuilderEnumerator` struct has been added in `StringBuilderEnumerator.cs` to enumerate the characters in a `StringBuilder`. This provides a new way to iterate through the characters in a `StringBuilder`. 7. A new `StringConcat` class has been added in `StringConcat.cs` to concatenate sequences of characters. This provides a new way to concatenate character sequences. 8. A new `StringSegmentEnumerator` struct has been added in `StringSegmentEnumerator.cs` to enumerate the characters in a `StringSegment`. This provides a new way to iterate through the characters in a `StringSegment`. 9. New tests for the `StringConcat` class have been added in `StringConcatTests.cs`. This ensures the `StringConcat` class functions as expected.
1 parent 5c1c284 commit 230c639

16 files changed

Lines changed: 803 additions & 115 deletions

.editorconfig

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ csharp_style_prefer_top_level_statements = true:silent
220220
csharp_style_prefer_local_over_anonymous_function = true:suggestion
221221
csharp_style_prefer_tuple_swap = true:suggestion
222222
csharp_style_prefer_utf8_string_literals = true:suggestion
223+
csharp_style_prefer_primary_constructors = true:suggestion
224+
dotnet_diagnostic.SYSLIB1045.severity = silent
223225

224226
[*.{cs,vb}]
225227
dotnet_style_operator_placement_when_wrapping = beginning_of_line
@@ -244,4 +246,7 @@ dotnet_style_prefer_inferred_tuple_names = true:suggestion
244246
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
245247
dotnet_style_prefer_compound_assignment = true:suggestion
246248
dotnet_style_prefer_simplified_interpolation = true:suggestion
247-
dotnet_style_namespace_match_folder = true:suggestion
249+
dotnet_style_namespace_match_folder = true:suggestion
250+
dotnet_diagnostic.CA1510.severity = silent
251+
dotnet_diagnostic.CA1512.severity = silent
252+
dotnet_diagnostic.CA1863.severity = none

Source/CharacterSequence.cs

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
using Microsoft.Extensions.Primitives;
2+
using System;
3+
using System.Buffers;
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Runtime.CompilerServices;
8+
using System.Text;
9+
using System.Diagnostics.CodeAnalysis;
10+
using System.Diagnostics;
11+
12+
namespace Open.Text;
13+
14+
/// <summary>
15+
/// A read-only struct that represents a sequence of characters.
16+
/// </summary>
17+
[SuppressMessage("Usage", "CA2225:Operator overloads have named alternates")]
18+
public readonly record struct CharacterSequence : IEnumerable<char>
19+
{
20+
private readonly int _length;
21+
private readonly Func<int>? _getLength;
22+
private readonly Func<string>? _toString;
23+
24+
/// <summary>
25+
/// Indicates whether the sequence is empty.
26+
/// </summary>
27+
/// <remarks>
28+
/// Can still return <see langword="false"/> if the sequence could have a length that potentially changes.
29+
/// </remarks>
30+
public bool IsEmpty => _length == 0;
31+
32+
/// <summary>
33+
/// The number of characters in the sequence.
34+
/// </summary>
35+
public readonly int Length
36+
=> _length == -1 ? _getLength!() : _length;
37+
38+
/// <summary>
39+
/// The characters in the sequence.
40+
/// </summary>
41+
public readonly IEnumerable<char> Characters { get; }
42+
43+
/// <summary>
44+
/// Constructs a CharacterSequence.
45+
/// </summary>
46+
private CharacterSequence(int length, IEnumerable<char> characters, Func<string>? toString = null)
47+
{
48+
Debug.Assert(length >= 0);
49+
50+
_length = length;
51+
_toString = toString;
52+
Characters = characters;
53+
}
54+
55+
private CharacterSequence(Func<int> getLength, IEnumerable<char> characters, Func<string>? toString = null)
56+
{
57+
_length = -1;
58+
_getLength = getLength;
59+
_toString = toString;
60+
Characters = characters;
61+
}
62+
63+
/// <summary>
64+
/// An empty CharacterSequence.
65+
/// </summary>
66+
public static CharacterSequence Empty { get; } = new(0, []);
67+
68+
/// <summary>
69+
/// Creates a new <see cref="CharacterSequence"/> from the specified characters.
70+
/// </summary>
71+
/// <remarks>Returns <see cref="Empty"/> if the <paramref name="length"/> is zero.</remarks>
72+
internal static CharacterSequence Create(int length, IEnumerable<char> characters)
73+
=> length == 0 ? Empty : new(length, characters);
74+
75+
/// <summary>
76+
/// Creates a new <see cref="CharacterSequence"/> from the specified characters.
77+
/// </summary>
78+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
79+
public static CharacterSequence Create(Func<int> getLength, IEnumerable<char> characters)
80+
=> new(getLength, characters);
81+
82+
/// <summary>
83+
/// Implicitly converts a <see cref="StringSegment"/> to a <see cref="CharacterSequence"/>.
84+
/// </summary>
85+
public static implicit operator CharacterSequence(string characters)
86+
{
87+
if (characters is null) return Empty;
88+
var len = characters.Length;
89+
return len == 0 ? Empty : new(len, characters, characters.ToString);
90+
}
91+
92+
/// <summary>
93+
/// Implicitly converts a <see cref="char"/> to a <see cref="CharacterSequence"/>.
94+
/// </summary>
95+
public static implicit operator CharacterSequence(char value)
96+
=> new(1, Enumerable.Repeat(value, 1));
97+
98+
/// <summary>
99+
/// Implicitly converts a <see cref="StringSegment"/> to a <see cref="CharacterSequence"/>.
100+
/// </summary>
101+
public static implicit operator CharacterSequence(StringSegment characters)
102+
{
103+
var len = characters.Length;
104+
return len == 0 ? Empty : new(len, characters.AsEnumerable(), characters.ToString);
105+
}
106+
107+
/// <summary>
108+
/// Implicitly converts a <see cref="ArraySegment{T}"/> of <see cref="char"/> to a <see cref="CharacterSequence"/>.
109+
/// </summary>
110+
public static implicit operator CharacterSequence(ArraySegment<char> characters)
111+
=> Create(characters.Count, characters);
112+
113+
/// <summary>
114+
/// Implicitly converts a <see cref="ArraySegment{T}"/> of <see cref="char"/> to a <see cref="CharacterSequence"/>.
115+
/// </summary>
116+
public static implicit operator CharacterSequence(char[] characters)
117+
{
118+
if (characters is null) return Empty;
119+
var len = characters.Length;
120+
return len == 0 ? Empty : new(len, characters, () => new string(characters));
121+
}
122+
123+
/// <summary>
124+
/// Implicitly converts a <see cref="ReadOnlyMemory{T}"/> of <see cref="char"/> to a <see cref="CharacterSequence"/>.
125+
/// </summary>
126+
public static implicit operator CharacterSequence(ReadOnlyMemory<char> characters)
127+
{
128+
var len = characters.Length;
129+
return len == 0 ? Empty : new(len, GetChars(characters), () => characters.Span.ToString());
130+
131+
static IEnumerable<char> GetChars(ReadOnlyMemory<char> memory)
132+
{
133+
var len = memory.Length;
134+
for (int i = 0; i < len; i++)
135+
yield return memory.Span[i];
136+
}
137+
}
138+
139+
/// <summary>
140+
/// Implicitly converts a <see cref="Memory{T}"/> of <see cref="char"/> to a <see cref="CharacterSequence"/>.
141+
/// </summary>
142+
public static implicit operator CharacterSequence(Memory<char> characters)
143+
=> (ReadOnlyMemory<char>)characters;
144+
145+
/// <summary>
146+
/// Implicitly converts a <see cref="ArraySegment{T}"/> of <see cref="char"/> to a <see cref="CharacterSequence"/>.
147+
/// </summary>
148+
public static implicit operator CharacterSequence(StringBuilder characters)
149+
=> characters is null ? Empty : new(() => characters.Length, characters.AsEnumerable(), characters.ToString);
150+
151+
/// <summary>
152+
/// Copies the characters in the sequence to a string.
153+
/// </summary>
154+
public override string ToString()
155+
{
156+
if (_toString is not null)
157+
return _toString();
158+
159+
var len = Length;
160+
if (len == 0) return string.Empty;
161+
162+
const int MaxStackSize = 256;
163+
if (len > MaxStackSize)
164+
{
165+
using var lease = MemoryPool<char>.Shared.Rent(len);
166+
var span = lease.Memory.Span.Slice(0, len);
167+
CopyTo(span);
168+
var result = span.ToString();
169+
span.Clear(); // This is important for security reasons.
170+
return result;
171+
}
172+
else
173+
{
174+
Span<char> allocated = stackalloc char[MaxStackSize];
175+
var span = allocated.Slice(0, len);
176+
CopyTo(span);
177+
return span.ToString();
178+
}
179+
}
180+
181+
/// <summary>
182+
/// Copies the characters in the sequence to a span.
183+
/// </summary>
184+
public void CopyTo(Span<char> span)
185+
{
186+
var e = Characters.GetEnumerator();
187+
for (int i = 0; i < span.Length; i++)
188+
{
189+
if (!e.MoveNext())
190+
throw new InvalidOperationException("The sequence is shorter than the specified length.");
191+
span[i] = e.Current;
192+
}
193+
194+
if (e.MoveNext())
195+
throw new InvalidOperationException("The sequence is longer than the specified length.");
196+
}
197+
198+
/// <inheritdoc />
199+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
200+
public IEnumerator<char> GetEnumerator() => Characters.GetEnumerator();
201+
202+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
203+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
204+
}

0 commit comments

Comments
 (0)