Я создаю собственное средство чтения ввода для синтаксического анализатора на С# и пытаюсь получить n-й символ из средства чтения потока, не перемещая позицию потока, чтобы при многократном вызове функции я получал один и тот же вывод (например, встроенная функция просмотра).
В настоящее время я могу получить n-й элемент, как показано в коде ниже, однако при этом я перемещаю позицию потоков, и поэтому при каждом вызове я получаю другой символ.
private StreamReader _reader;
private int _peekChar = -1;
public InputReader(Stream inputStream)
{
_reader = new StreamReader(inputStream);
}
// Returns the next character in the input stream without consuming it
public char Peek()
{
// Checks if character already peeked
if (_peekChar == -1)
_peekChar = _reader.Peek();
return (char)_peekChar;
}
// Returns the n th next character in the input stream without consuming it
public char Peek(int n)
{
if (n <= 0)
throw new ArgumentException("n must be a positive integer.");
char[] buffer = new char[n];
int bytesRead = _reader.Read(buffer, 0, n);
if (bytesRead < n)
throw new EndOfStreamException("Not enough characters in the stream to peek.");
return buffer[n - 1];
}
Учитывая, что я вызываю Peek(1) 3 раза в потоке с "<HTML> <p> test <p/> <HTML/>"
, я ожидаю 3 вывода <
, однако получаю <
H
T
Когда ты
_reader.Read(buffer, 0, n);
Затем позиция потока увеличивается.
Вам нужно переместить позицию потока обратно после чтения (если поток это поддерживает)
_reader.Seek(-bytesRead, SeekOrigin.End);
Стоит отметить, что Seek
выдаст исключение, если CanSeek
является false
для Stream
, поэтому код должен сначала проверить CanSeek
и вызвать Read
тогда и только тогда, когда это true
. Peek
, вероятно, следует бросить InvalidOperationException
, если CanSeek
есть false
.
Кроме того, _reader
— это StreamReader
, у которого нет метода Seek
. Вы бы звонили Seek
на _reader.BaseStream
.
В этой ситуации я бы написал «PeekableStream», который поддерживает буфер, в который вы можете заглянуть. Он будет извлекать символы из буфера, если он доступен, а затем из исходного потока, когда буфер исчерпан. Буфер расширяется и заполняется на основе числа «n», переданного методу Peek.
Как бы мне это сделать?
Основываясь на ответе, предоставленном divinci, я обновил свою функцию до
public char Peek(int n)
{
if (n < 0)
throw new ArgumentException("k must be a positive integer.");
long originalPosition = _reader.BaseStream.Position;
char[] buffer = new char[n + 1];
// Read characters up to the n-th position
int readChars = _reader.Read(buffer, 0, n + 1);
_reader.BaseStream.Seek(originalPosition, SeekOrigin.Begin);
_reader.DiscardBufferedData();
if (readChars < n + 1)
throw new EndOfStreamException("Not enough characters in the stream to peek.");
return buffer[n];
}
Затем у меня возникла проблема с возможностью циклического выполнения функции, и я обнаружил, что всякий раз, когда я использовал _reader.Peek()
, она меняла положение потока. Я решил это, используя
long originalPosition = _reader.BaseStream.Position;
int nextChar = _reader.Peek();
_reader.BaseStream.Seek(originalPosition, SeekOrigin.Begin);
когда бы я ни позвонил _reader.Peek()
Вам небезразлично использование StreamReader.Read, который также перемещает позицию читателя по счетчику (n в вашем коде), даже если это не задокументировано для его перегрузки (посмотрел декомпилированный код).