Ответ 1
Нужно ли добавлять что-то в пользовательский поток, чтобы сообщить, что все данные были прочитаны?
Вы можете, но это не помогло бы в сценарии WCF, где полученный Stream
- это другой класс.
Существует два стандартных (официальных, по дизайну) способа определения конца данных Stream
:
(1) ReadByte возвращает -1
Возвращает
Беззнаковый байт передается в Int32 или -1, если в конце потока.
(2) Чтение возвращаемого 0 при вызове с count > 0
Возвращает
Общее количество байтов, считанных в буфере. Это может быть меньше количества запрошенных байтов, если количество байтов в настоящий момент недоступно, или нуль (0), если конец потока достигнут.
К сожалению, оба они потребляют текущий байт (переход к следующему) и разрушают десериализатор.
Каковы возможные решения?
Во-первых, реализация некоторого формата (протокола) сериализации/десериализации, который позволяет вам узнать, есть ли больше элементов для десериализации. например, List<T>
сохраняет Count
перед элементами, T[]
сохраняет Length
перед элементами и т.д. Так как EnumerableStream<T>
не знает счет заранее, одно простое решение состоит в том, чтобы выпустить один поддельный байт перед каждым элементом:
private bool SerializeNext()
{
if (!_source.MoveNext())
return false;
buf.Enqueue(1); // <--
foreach (var b in _serializer(_source.Current))
_buf.Enqueue(b);
return true;
}
Это позволит вам использовать
while (stream.ReadByte() != -1)
{
// ...
}
Во-вторых, если вы хотите сохранить текущий формат, более общим решением было бы реализовать пользовательский поток, который переносит другой поток и реализует метод PeekByte
с той же семантикой, что и стандартный ReadByte
, но без использования текущего байта:
public class SequentialStream : Stream
{
private Stream source;
private bool leaveOpen;
private int? nextByte;
public SequentialStream(Stream source, bool leaveOpen = false)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (!source.CanRead) throw new ArgumentException("Non readable source.", nameof(source));
this.source = source;
this.leaveOpen = leaveOpen;
}
protected override void Dispose(bool disposing)
{
if (disposing && !leaveOpen)
source.Dispose();
base.Dispose(disposing);
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override void Flush() { }
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public int PeekByte()
{
if (nextByte == null)
nextByte = source.ReadByte();
return nextByte.Value;
}
public override int Read(byte[] buffer, int offset, int count)
{
if (count <= 0) return 0;
if (nextByte != null)
{
if (nextByte.Value < 0) return 0;
buffer[offset] = (byte)nextByte.Value;
if (count > 1)
{
int read = source.Read(buffer, offset + 1, count - 1);
if (read == 0)
nextByte = -1;
else
nextByte = null;
return read + 1;
}
else
{
nextByte = null;
return 1;
}
}
else
{
int read = source.Read(buffer, offset, count);
if (read == 0)
nextByte = -1;
return read;
}
}
}
Это в основном реализует только поток только для чтения, только с 0 или 1 байтом.
Использование будет выглядеть так:
using (var stream = new SequentialStream(GetStream(ListToSend)))
{
// ...
while (stream.PeekByte() != -1)
{
// ...
}
// ...
}
PS Как насчет
Я также хочу знать, почему, когда я помещаю точку прерывания в функцию чтения, размер буфера изменяется случайным образом.
Это не случайно. BinaryFormatter
внутренне использует BinaryReader
для чтения типизированных значений, таких как Int32
, Byte
, String
и т.д., BinaryReader
нужный размер в виде count
, например 4, 1, количество строковых кодированных байтов (которое он знает, поскольку хранит их в потоке до фактических данных и читает его, прежде чем пытаться прочитать фактические данные) и т.д.