Use Readers and Writers

Select a byte protocol or text policy deliberately instead of relying on familiar .NET type names.

IntermediateIOSource-checked

Choose binary or text deliberately

Readers and writers add a protocol over a Stream. BinaryReader/BinaryWriter define primitive layout and string length prefixes. StreamReader/StreamWriter expose text-shaped methods but have a narrower encoding model at this pin. StringReader/StringWriter work entirely with native strings.

Binary protocol

Use BinaryReader/Writer when both sides agree on Sharp Runtime’s format: little-endian integers/floats, one-byte booleans, UTF-8 strings with 7-bit encoded byte lengths, and explicit read order. Both borrow a raw Stream* and take an optional leaveOpen.

System::IO::MemoryStream stream;
System::IO::BinaryWriter writer(&stream, true);

writer.Write(static_cast<SharpRuntime::uintcs>(0x5352));
writer.Write(true);
writer.Write(std::string("payload"));
writer.Flush();

stream.setPositionProperty(0);
System::IO::BinaryReader reader(&stream, true);

const auto magic = reader.ReadUInt32();
const auto enabled = reader.ReadBoolean();
const auto payload = reader.ReadString();

Do not serialize native structs by dumping their bytes. Padding, endianness, alignment, ABI, pointer fields, and object invariants make that format unstable and often unsafe.

Character reads in BinaryReader

BinaryReader’s character APIs decode UTF-8 and return charcs UTF-16 code units. A scalar outside the Basic Multilingual Plane is exposed as a surrogate pair across two reads. PeekChar requires a seekable stream because it decodes then restores position.

This differs from StreamReader, whose current lightweight implementation reads individual bytes as Latin-1/ASCII-like characters and does not detect BOMs or decode multibyte UTF-8.

StreamWriter writes existing native bytes

StreamWriter writes the bytes already stored in the native std::string. In normal project convention those bytes are UTF-8. It does not run an arbitrary Encoding pipeline in the current constructor surface. Convert/validate text explicitly before writing if the external format requires another encoding.

StreamReader limitations

OperationCurrent behaviorDo not assume
Peek/ReadOne input byte as an integerOne Unicode scalar or UTF-16 unit
ReadLineByte-oriented line terminatorsBOM or encoding detection
ReadToEndRemaining raw bytes as stringMultibyte decoding validation
ConstructionRejects null or unreadable streamAutomatic recovery from invalid stream state

Close and leave-open ownership

With leaveOpen=false, destruction and Close close the underlying stream. With leaveOpen=true, the wrapper borrows it. There is a current lifecycle reduction: calling Close on StreamReader or StreamWriter does not mark the wrapper itself closed. With leave-open true, later wrapper operations can still succeed. Do not depend on this deviation; stop using the wrapper after Close.

StringReader and StringWriter

Use these for tests, templating, or transformations that do not need a byte stream. StringReader owns a copy of its input and handles LF, CRLF, and lone CR line endings. StringWriter owns an ostringstream and returns the accumulated value through ToString/GetStringBuilder. Their inherited Close behavior is effectively a no-op, another reason to scope rather than reuse closed wrappers.

Protocol checklist

  • Record endianness, string encoding, length prefix, version, and maximum sizes.
  • Reject oversized declared lengths before allocation where possible.
  • Test partial streams and clean EOF separately from truncation.
  • Choose one owner for the Stream and set leaveOpen accordingly.
  • Do not mix byte positions with decoded character positions.