Bia.ValueBuffers
0.9.0
dotnet add package Bia.ValueBuffers --version 0.9.0
NuGet\Install-Package Bia.ValueBuffers -Version 0.9.0
<PackageReference Include="Bia.ValueBuffers" Version="0.9.0" />
<PackageVersion Include="Bia.ValueBuffers" Version="0.9.0" />
<PackageReference Include="Bia.ValueBuffers" />
paket add Bia.ValueBuffers --version 0.9.0
#r "nuget: Bia.ValueBuffers, 0.9.0"
#:package Bia.ValueBuffers@0.9.0
#addin nuget:?package=Bia.ValueBuffers&version=0.9.0
#tool nuget:?package=Bia.ValueBuffers&version=0.9.0
Bia.ValueBuffers
High-performance, stack-first value-type buffers for .NET. Zero-allocation where possible, ArrayPool-backed overflow. Cross-platform, trimmable and AOT/NativeAOT compatible.
⭐ Please star this project if you like it. ⭐
Example | ValueStringBuilder | ValueRingBuffer | ValueByteBuffer | ValueTokenizer | Public API
Example
// ValueStringBuilder — stack-first string building, zero allocation on the happy path
Span<char> buf = stackalloc char[128];
var sb = new ValueStringBuilder(buf);
sb.Append("Count: ");
sb.Append(42); // formats int directly, no alloc
sb.Append(", pi: ");
sb.Append(3.14159, "F4"); // formats double with format string
string message = sb.ToString(); // materializes string and returns pooled memory
// ValueRingBuffer<T> — fixed-capacity circular buffer backed by stackalloc
Span<int> ring = stackalloc int[4];
var rb = new ValueRingBuffer<int>(ring);
rb.Write(1);
rb.Write(2);
rb.Write(3);
rb.Write(4);
rb.Write(5); // buffer is full — overwrites oldest element (1)
int oldest = rb.Read(); // 2 (FIFO)
// ValueByteBuffer — stack-first binary data assembly
Span<byte> bytes = stackalloc byte[16];
using var bb = new ValueByteBuffer(bytes);
bb.WriteUInt32LittleEndian(0xDEADBEEF);
bb.Write((byte)0xFF);
byte[] packet = bb.ToArray(); // snapshot to heap; bb is disposed at end of scope
// ValueTokenizer — zero-allocation span-based tokenizer
var tokenizer = new ValueTokenizer("{one}{two}{three}".AsSpan());
while (tokenizer.TryReadNext(out ReadOnlySpan<char> token))
_ = token; // process token as span, no heap allocation
_ = message;
_ = oldest;
_ = packet;
For more examples see Example Catalogue.
Benchmarks
Run dotnet Build.cs comparison-bench then dotnet test to populate results locally.
Detailed Benchmarks
Comparison Benchmarks
TestBench Benchmark Results
Example Catalogue
The following examples are available in ReadMeTest.cs.
Example - Overview
// ValueStringBuilder — stack-first string building, zero allocation on the happy path
Span<char> buf = stackalloc char[128];
var sb = new ValueStringBuilder(buf);
sb.Append("Count: ");
sb.Append(42); // formats int directly, no alloc
sb.Append(", pi: ");
sb.Append(3.14159, "F4"); // formats double with format string
string message = sb.ToString(); // materializes string and returns pooled memory
// ValueRingBuffer<T> — fixed-capacity circular buffer backed by stackalloc
Span<int> ring = stackalloc int[4];
var rb = new ValueRingBuffer<int>(ring);
rb.Write(1);
rb.Write(2);
rb.Write(3);
rb.Write(4);
rb.Write(5); // buffer is full — overwrites oldest element (1)
int oldest = rb.Read(); // 2 (FIFO)
// ValueByteBuffer — stack-first binary data assembly
Span<byte> bytes = stackalloc byte[16];
using var bb = new ValueByteBuffer(bytes);
bb.WriteUInt32LittleEndian(0xDEADBEEF);
bb.Write((byte)0xFF);
byte[] packet = bb.ToArray(); // snapshot to heap; bb is disposed at end of scope
// ValueTokenizer — zero-allocation span-based tokenizer
var tokenizer = new ValueTokenizer("{one}{two}{three}".AsSpan());
while (tokenizer.TryReadNext(out ReadOnlySpan<char> token))
_ = token; // process token as span, no heap allocation
_ = message;
_ = oldest;
_ = packet;
Example - ValueStringBuilder
// Append text, numbers, and formatted values — all without intermediate allocations
Span<char> buf = stackalloc char[128];
var sb = new ValueStringBuilder(buf);
sb.Append("Count: ");
sb.Append(42); // int — no boxing, no ToString allocation
sb.Append(", ratio: ");
sb.Append(0.5, "P0"); // double with format string
sb.AppendLine();
sb.Append("Guid: ");
sb.Append(Guid.Empty); // any ISpanFormattable, zero alloc
string result = sb.ToString(); // materializes and disposes
// WrittenSpan: inspect built content without allocating a string
Span<char> buf2 = stackalloc char[64];
var sb2 = new ValueStringBuilder(buf2);
sb2.Append("hello");
sb2.Append(' ');
sb2.Append("world");
ReadOnlySpan<char> view = sb2.WrittenSpan; // zero-alloc view of "hello world"
_ = view; // pass to Encoding, TextWriter, comparers — no string needed
sb2.Dispose();
// AppendEnclosedJoin: format span-backed numeric sequences without intermediate strings
var items = new[] { 1, 2, 3 };
Span<char> buf3 = stackalloc char[64];
var sb3 = new ValueStringBuilder(buf3);
sb3.AppendEnclosedJoin("[".AsSpan(), ", ".AsSpan(), "]".AsSpan(), items, "D2");
string joined = sb3.ToString(); // "[01, 02, 03]"
// IEnumerable<T> enclosed joins can now stay bounded without dropping to manual loops
IEnumerable<int> ids = [7, 42, 99, 120];
Span<char> buf4 = stackalloc char[64];
var sb4 = new ValueStringBuilder(buf4);
sb4.AppendEnclosedJoin(
"[".AsSpan(),
", ".AsSpan(),
"]".AsSpan(),
ids,
maxItems: 3,
overflowSuffix: " more".AsSpan()
);
string boundedIds = sb4.ToString(); // "[7, 42, 99, +1 more]"
// AppendPadded keeps fixed-width columns readable without interpolation fallback
Span<char> buf5 = stackalloc char[64];
var sb5 = new ValueStringBuilder(buf5);
sb5.AppendPadded("slot".AsSpan(), 8);
sb5.AppendPadded(12, 4, leftAlign: false, padChar: '0');
string padded = sb5.ToString(); // "slot 0012"
// Standard interpolation alignment now works directly on the builder handler
Span<char> buf6 = stackalloc char[64];
var sb6 = new ValueStringBuilder(buf6);
string label = "slot";
int count = 12;
sb6.Append($"[{label, 6}] {count, 4:D2}");
string aligned = sb6.ToString(); // "[ slot] 12"
// Formatter structs are still the better fit when each item needs custom layout
byte[] payload = [0xDE, 0xAD, 0xBE, 0xEF];
Span<char> buf7 = stackalloc char[32];
var sb7 = new ValueStringBuilder(buf7);
sb7.AppendJoin(" | ".AsSpan(), items, new SlotFormatter());
string formattedSlots = sb7.ToString(); // "#01 | #02 | #03"
// Span-based hex helpers now support prefixed byte dumps directly on the builder
Span<char> buf8 = stackalloc char[32];
var sb8 = new ValueStringBuilder(buf8);
sb8.AppendHex(payload, "0x".AsSpan(), "-".AsSpan());
string hex = sb8.ToString(); // "0xDE-AD-BE-EF"
// WriteEncodedTo: bridge staged text directly into a caller-owned byte buffer
Span<char> buf9 = stackalloc char[32];
var sb9 = new ValueStringBuilder(buf9);
sb9.Append("Caf\u00E9");
Span<byte> bytes = stackalloc byte[16];
var bb = new ValueByteBuffer(bytes);
sb9.WriteEncodedTo(Encoding.UTF8, ref bb);
ReadOnlySpan<byte> utf8 = bb.WrittenSpan; // [43 61 66 C3 A9]
_ = result;
_ = joined;
_ = boundedIds;
_ = padded;
_ = aligned;
_ = formattedSlots;
_ = hex;
_ = utf8;
bb.Dispose();
sb9.Dispose();
Use the IEnumerable<T> join overloads when the source sequence shape is the only obstacle and each element already implements ISpanFormattable. Use a formatter struct when each element needs extra punctuation, labels, or mixed formatting logic.
Example - ValueRingBuffer
// Circular overwrite: oldest element is silently dropped when full
Span<int> ring = stackalloc int[3];
var rb = new ValueRingBuffer<int>(ring);
rb.Write(10);
rb.Write(20);
rb.Write(30);
rb.Write(40); // 10 is overwritten; buffer now holds [20, 30, 40]
int first = rb.Read(); // 20 — FIFO order
// Indexer: random-access without removal (0 = oldest)
Span<int> ring2 = stackalloc int[4];
var rb2 = new ValueRingBuffer<int>(ring2);
rb2.Write(1);
rb2.Write(2);
rb2.Write(3);
int oldest = rb2[0]; // 1
int newest = rb2[rb2.Count - 1]; // 3
// ToArray: snapshot FIFO contents to a heap array
int[] snapshot = rb2.ToArray(); // [1, 2, 3]
// Drain: copy FIFO contents to a span and reset count to zero
Span<int> dest = stackalloc int[rb2.Count];
rb2.Drain(dest); // dest == [1, 2, 3]; rb2.Count == 0
_ = first;
_ = oldest;
_ = newest;
_ = snapshot;
Example - ValueByteBuffer
// Assemble a binary packet on the stack — no heap allocations until ToArray/CopyTo
Span<byte> buf = stackalloc byte[64];
using var bb = new ValueByteBuffer(buf);
bb.WriteUInt16LittleEndian(0x0102); // 2 bytes: message type
bb.WriteInt32LittleEndian(42); // 4 bytes: payload length
bb.WriteUInt64LittleEndian(ulong.MaxValue); // 8 bytes: sequence number
bb.Write((byte)0xFF); // 1 byte: flags
int length = bb.Length; // 15 — zero heap allocations so far
byte[] copy = bb.ToArray(); // snapshot to heap only when needed
// WriteEncoded: append encoded text directly to the byte buffer with any Encoding
Span<byte> buf2 = stackalloc byte[32];
using var bb2 = new ValueByteBuffer(buf2);
bb2.Write((byte)0x01); // record type
bb2.WriteEncoded("Caf\u00E9".AsSpan(), Encoding.Latin1);
ReadOnlySpan<byte> encoded = bb2.WrittenSpan; // [01 43 61 66 E9]
// WriteUtf8: format UTF-8-first values directly into the byte buffer without a char intermediary
Span<byte> buf3 = stackalloc byte[32];
using var bb3 = new ValueByteBuffer(buf3);
bb3.Write((byte)0x02); // record type
bb3.WriteUtf8(255, "X4");
ReadOnlySpan<byte> utf8Formatted = bb3.WrittenSpan; // [02 30 30 46 46]
// WriteTo: push the written span to a stream without first calling ToArray()
using var stream = new MemoryStream();
bb3.WriteTo(stream);
byte[] streamed = stream.ToArray();
_ = length;
_ = copy;
_ = encoded;
_ = utf8Formatted;
_ = streamed;
Example - ValueTokenizer
// Extract flat tokens — no heap allocations during scanning
var tokenizer = new ValueTokenizer("{hello}{world}".AsSpan());
while (tokenizer.TryReadNext(out ReadOnlySpan<char> token))
_ = token; // use as ReadOnlySpan<char>; call .ToString() only when string is needed
// Custom delimiters — parentheses as open/close
var csv = new ValueTokenizer("(Alice)(Bob)(Carol)".AsSpan(), '(', ')');
csv.TryReadNext(out ReadOnlySpan<char> name0); // "Alice"
csv.TryReadNext(out ReadOnlySpan<char> name1); // "Bob"
csv.TryReadNext(out ReadOnlySpan<char> name2); // "Carol"
// Nested delimiters — inner pairs counted as one token
var src = new ValueTokenizer("{group {detail} text}".AsSpan());
src.TryReadNested(out ReadOnlySpan<char> nested); // "group {detail} text"
// Position tracking and reset
var t = new ValueTokenizer("{a}{b}".AsSpan());
t.TryReadNext(out _); // consumes {a}
int pos = t.Position; // 3 (past the closing brace)
t.Reset(); // back to 0
_ = name0;
_ = name1;
_ = name2;
_ = nested;
_ = pos;
Public API Reference
See docs/PublicApi.md for the complete auto-generated public API reference.
Note:
docs/PublicApi.mdis auto-updated by theReadMeTest_PublicApitest on everydotnet testrun. Do not edit it manually.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Bia.ValueBuffers:
| Package | Downloads |
|---|---|
|
Maple.Text
MapleStory rich-text parsing: tokenizes and strips MapleStory custom markup for use in UI text rendering. Cross-platform, trimmable and AOT/NativeAOT compatible. |
GitHub repositories
This package is not used by any popular GitHub repositories.