How do you split multi-line string into lines?
I know this way
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
looks a bit ugly and loses empty lines. Is there a better solution?
How do you split multi-line string into lines?
I know this way
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
looks a bit ugly and loses empty lines. Is there a better solution?
It's tricky to handle mixed line endings properly. As we know, the line termination characters can be "Line Feed" (ASCII 10, \n, \x0A, \u000A), "Carriage Return" (ASCII 13, \r, \x0D, \u000D), or some combination of them. Going back to DOS, Windows uses the two-character sequence CR-LF \u000D\u000A, so this combination should only emit a single line. Unix uses a single \u000A, and very old Macs used a single \u000D character. The standard way to treat arbitrary mixtures of these characters within a single text file is as follows:
\u000D\u000A) then these two together skip just one line.String.Empty is the only input that returns no lines (any character entails at least one line)The preceding rule describes the behavior of StringReader.ReadLine and related functions, and the function shown below produces identical results. It is an efficient C# line breaking function that dutifully implements these guidelines to correctly handle any arbitrary sequence or combination of CR/LF. The enumerated lines do not contain any CR/LF characters. Empty lines are preserved and returned as String.Empty.
/// <summary>
/// Enumerates the text lines from the string.
/// ⁃ Mixed CR-LF scenarios are handled correctly
/// ⁃ String.Empty is returned for each empty line
/// ⁃ No returned string ever contains CR or LF
/// </summary>
public static IEnumerable<String> Lines(this String s)
{
int j = 0, c, i;
char ch;
if ((c = s.Length) > 0)
do
{
for (i = j; (ch = s[j]) != '\r' && ch != '\n' && ++j < c;)
;
yield return s.Substring(i, j - i);
}
while (++j < c && (ch != '\r' || s[j] != '\n' || ++j < c));
}
Note: If you don't mind the overhead of creating a StringReader instance on each call, you can use the following C# 7 code instead. As noted, while the example above may be slightly more efficient, both of these functions produce the exact same results.
public static IEnumerable<String> Lines(this String s)
{
using (var tr = new StringReader(s))
while (tr.ReadLine() is String L)
yield return L;
}
Split a string into lines without any allocation.
public static LineEnumerator GetLines(this string text) {
return new LineEnumerator( text.AsSpan() );
}
internal ref struct LineEnumerator {
private ReadOnlySpan<char> Text { get; set; }
public ReadOnlySpan<char> Current { get; private set; }
public LineEnumerator(ReadOnlySpan<char> text) {
Text = text;
Current = default;
}
public LineEnumerator GetEnumerator() {
return this;
}
public bool MoveNext() {
if (Text.IsEmpty) return false;
var index = Text.IndexOf( '\n' ); // \r\n or \n
if (index != -1) {
Current = Text.Slice( 0, index + 1 );
Text = Text.Slice( index + 1 );
return true;
} else {
Current = Text;
Text = ReadOnlySpan<char>.Empty;
return true;
}
}
}
late to the party, but I've been using a simple collection of extension methods for just that, which leverages TextReader.ReadLine():
public static class StringReadLinesExtension
{
public static IEnumerable<string> GetLines(this string text) => GetLines(new StringReader(text));
public static IEnumerable<string> GetLines(this Stream stm) => GetLines(new StreamReader(stm));
public static IEnumerable<string> GetLines(this TextReader reader) {
string line;
while ((line = reader.ReadLine()) != null)
yield return line;
reader.Dispose();
yield break;
}
}
Using the code is really trivial:
// If you have the text as a string...
var text = "Line 1\r\nLine 2\r\nLine 3";
foreach (var line in text.GetLines())
Console.WriteLine(line);
// You can also use streams like
var fileStm = File.OpenRead("c:\tests\file.txt");
foreach(var line in fileStm.GetLines())
Console.WriteLine(line);
Hope this helps someone out there.