Use Readers and Writers
Select a byte protocol or text policy deliberately instead of relying on familiar .NET type names.
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
| Operation | Current behavior | Do not assume |
|---|---|---|
Peek/Read | One input byte as an integer | One Unicode scalar or UTF-16 unit |
ReadLine | Byte-oriented line terminators | BOM or encoding detection |
ReadToEnd | Remaining raw bytes as string | Multibyte decoding validation |
| Construction | Rejects null or unreadable stream | Automatic 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
leaveOpenaccordingly. - Do not mix byte positions with decoded character positions.