Simple streaming parser to extract lines It is meant to be given strings over time and output all NewLine delimited substrings. The ColumnReader is meant to be a solution for Delimiter-separated-value files, like CSV or TSV.

using System;

namespace fbstj
{
    public sealed class LineReader
    {
        public string NewLine { get; set; }

        string _buffer = "";

        public void Parse(string text)
        {
            _buffer += text;

            var lastline = _buffer.LastIndexOf(NewLine);
            if (lastline == -1)
                return;

            var lines = _buffer.Substring(0, lastline).Split(NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            _buffer = _buffer.Substring(lastline);

            foreach (var line in lines)
                Receive(line);
        }

        public event Action&lt;string&gt; Receive = (line) =&gt; { };
    }

    public struct ColumnReader
    {
        public readonly string Delimiter;
        public readonly LineReader Reader;

        public ColumnReader(string newline, string delimiter) : this()
        {
            Reader = new LineReader { NewLine = newline };
            Delimiter = delimiter;
            Receive += (columns) =&gt; { };
            Reader.Receive += _receive_line;
        }

        private void _receive_line(string line)
        {
            Receive(line.Split(Delimiter.ToCharArray()));
        }

        public event Action&lt;string[]&gt; Receive;
    }
}


My main use-case is reading lines from a SerialPort:

var parser = new IO.LineReader { NewLine = port.NewLine };
port.DataReceived += (o, e) =&gt; parser.Parse(port.ReadExisting());
parser.Receive += (line) =&gt; { };
This looks like they used to be fields and then you made them properties:

public readonly string Delimiter;
public readonly LineReader Reader;


IMHO that means they should get a getter and setter, like this:

public string Delimiter { get; private set; }
public LineReader Reader { get; private set; }




String.LastIndexOf is culture-specific, so consider using a StringComparison.