// // StringExtensions.cs // // Copyright (c) František Boháček. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace NosSmooth.Cryptography.Extensions; /// /// Extension methods for string. /// public static class StringExtensions { /// /// Split a string into lines without allocations. /// /// The string to split. /// An enumerator with lines. public static LineSplitEnumerator SplitLines(this string str) { // LineSplitEnumerator is a struct so there is no allocation here return new LineSplitEnumerator(str.AsSpan()); } /// /// Split a string without any allocation. /// /// The string to split. /// The char to split with. /// The split enumerator. public static SplitEnumerator SplitAllocationless(this string str, char split) { return new SplitEnumerator(str.AsSpan(), split); } /// /// An enumerator of a string split. /// public ref struct SplitEnumerator { private readonly char _split; private ReadOnlySpan _str; /// /// Initializes a new instance of the struct. /// /// The string. /// The split char. public SplitEnumerator(ReadOnlySpan str, char split) { _str = str; _split = split; Current = default; } /// /// Gets this enumerator. /// /// This. public SplitEnumerator GetEnumerator() => this; /// /// Move to next line. /// /// Whether move was successful. public bool MoveNext() { var span = _str; if (span.Length == 0) { return false; } var index = span.IndexOf(_split); if (index == -1) { _str = ReadOnlySpan.Empty; Current = span; return true; } Current = span.Slice(0, index); _str = span.Slice(index + 1); return true; } /// /// Current line. /// public ReadOnlySpan Current { get; private set; } } /// /// An enumerator of a string lines. /// public ref struct LineSplitEnumerator { private ReadOnlySpan _str; /// /// Initializes a new instance of the struct. /// /// The string. public LineSplitEnumerator(ReadOnlySpan str) { _str = str; Current = default; } /// /// Gets this enumerator. /// /// This. public LineSplitEnumerator GetEnumerator() => this; /// /// Move to next line. /// /// Whether move was successful. public bool MoveNext() { var span = _str; if (span.Length == 0) { return false; } var index = span.IndexOfAny('\r', '\n'); if (index == -1) { _str = ReadOnlySpan.Empty; Current = new LineSplitEntry(span, ReadOnlySpan.Empty); return true; } if (index < span.Length - 1 && span[index] == '\r') { var next = span[index + 1]; if (next == '\n') { Current = new LineSplitEntry(span.Slice(0, index), span.Slice(index, 2)); _str = span.Slice(index + 2); return true; } } Current = new LineSplitEntry(span.Slice(0, index), span.Slice(index, 1)); _str = span.Slice(index + 1); return true; } /// /// Current line. /// public LineSplitEntry Current { get; private set; } } /// /// A line. /// public readonly ref struct LineSplitEntry { /// /// Initializes a new instance of the struct. /// /// The line. /// The line separator. public LineSplitEntry(ReadOnlySpan line, ReadOnlySpan separator) { Line = line; Separator = separator; } /// /// Gets the line. /// public ReadOnlySpan Line { get; } /// /// Gets the separator of the line. /// public ReadOnlySpan Separator { get; } /// /// This method allow to deconstruct the type, so you can write any of the following code /// foreach (var entry in str.SplitLines()) { _ = entry.Line; } /// foreach (var (line, endOfLine) in str.SplitLines()) { _ = line; } /// https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct?WT.mc_id=DT-MVP-5003978#deconstructing-user-defined-types. /// /// The line. /// The line separator. public void Deconstruct(out ReadOnlySpan line, out ReadOnlySpan separator) { line = Line; separator = Separator; } /// /// An implicit cast to ReadOnySpan. /// /// The entry to cast. /// The read only span of the entry. public static implicit operator ReadOnlySpan(LineSplitEntry entry) => entry.Line; } }