Баллы, уже сделанные в отношении BaseStream, являются действительными и важными. Тем не менее, существуют ситуации, в которых вы хотите прочитать текст и знать, где в тексте вы находитесь. Все еще может быть полезно записать это как класс, чтобы облегчить его повторное использование.
Я пытался написать такой класс сейчас. Кажется, работает правильно, но довольно медленно. Это должно быть хорошо, когда производительность не имеет решающего значения (это не , что медленно, см. Ниже).
Я использую ту же логику для отслеживания положения в тексте независимо от того, читаете ли вы символ за раз, один буфер за раз или одну строку за раз. Хотя я уверен, что если отказаться от этого, то это можно будет сделать для более эффективной работы, это значительно упростило реализацию ... и, я надеюсь, следовать коду.
Я сделал очень простое сравнение производительности метода ReadLine (который я считаю самым слабым местом этой реализации) с StreamReader, и разница почти на порядок. Я получил 22 МБ / с, используя мой класс StreamReaderEx, но почти в 9 раз больше, если использовать StreamReader напрямую (на моем ноутбуке с SSD). Хотя это может быть интересно, я не знаю, как правильно провести тест на чтение; может быть, использовать 2 одинаковых файла, каждый больше, чем размер буфера диска, и читать их попеременно ... По крайней мере, мой простой тест дает последовательные результаты, когда я запускаю его несколько раз, и независимо от того, какой класс читает файл теста первым.
Символом NewLine по умолчанию является Environment.NewLine, но может быть задана любая строка длиной 1 или 2. Читатель рассматривает только этот символ как символ новой строки, что может быть недостатком. По крайней мере, я знаю, что Visual Studio довольно много раз подсказывала мне, что открываемый мной файл "содержит несовместимые символы новой строки".
Обратите внимание, что я не включил класс гвардии; это простой служебный класс, и из контекста должно быть понятно, как его заменить. Вы можете даже удалить его, но вы потеряете некоторую проверку аргументов, и, следовательно, полученный код будет дальше от «правильного». Например, Guard.NotNull (s, "s") просто проверяет, что s не равно NULL, и выдает исключение ArgumentNullException (с именем аргумента "s", следовательно, вторым параметром), если это так.
Хватит болтать, вот код:
public class StreamReaderEx : StreamReader
{
// NewLine characters (magic value -1: "not used").
int newLine1, newLine2;
// The last character read was the first character of the NewLine symbol AND we are using a two-character symbol.
bool insideNewLine;
// StringBuilder used for ReadLine implementation.
StringBuilder lineBuilder = new StringBuilder();
public StreamReaderEx(string path, string newLine = "\r\n") : base(path)
{
init(newLine);
}
public StreamReaderEx(Stream s, string newLine = "\r\n") : base(s)
{
init(newLine);
}
public string NewLine
{
get { return "" + (char)newLine1 + (char)newLine2; }
private set
{
Guard.NotNull(value, "value");
Guard.Range(value.Length, 1, 2, "Only 1 to 2 character NewLine symbols are supported.");
newLine1 = value[0];
newLine2 = (value.Length == 2 ? value[1] : -1);
}
}
public int LineNumber { get; private set; }
public int LinePosition { get; private set; }
public override int Read()
{
int next = base.Read();
trackTextPosition(next);
return next;
}
public override int Read(char[] buffer, int index, int count)
{
int n = base.Read(buffer, index, count);
for (int i = 0; i