Faactory.Channels.Buffers
2.8.2
dotnet add package Faactory.Channels.Buffers --version 2.8.2
NuGet\Install-Package Faactory.Channels.Buffers -Version 2.8.2
<PackageReference Include="Faactory.Channels.Buffers" Version="2.8.2" />
<PackageVersion Include="Faactory.Channels.Buffers" Version="2.8.2" />
<PackageReference Include="Faactory.Channels.Buffers" />
paket add Faactory.Channels.Buffers --version 2.8.2
#r "nuget: Faactory.Channels.Buffers, 2.8.2"
#:package Faactory.Channels.Buffers@2.8.2
#addin nuget:?package=Faactory.Channels.Buffers&version=2.8.2
#tool nuget:?package=Faactory.Channels.Buffers&version=2.8.2
Channels - Buffers
A lightweight library for reading and writing byte buffers.
Although originally designed for Channels and part of the same project, this package has no dependency on Channels and can be used independently.
Design
Buffer instances are always specialized for either reading or writing, never both.
- Use
IReadableByteBufferfor reading - Use
IWritableByteBufferfor writing
This separation allows each implementation to be optimized for its specific purpose.
Using Buffers
When using Channels, you usually don’t need to manually create buffer instances. The pipeline can automatically convert between byte[] and IReadableByteBuffer.
Outside of Channels, or when needed explicitly:
- Use
WritableByteBufferto create a writable buffer. - Use
ReadableByteBufferto wrap abyte[].
Reading Data
There are four ways to retrieve data:
- Get performs a passive read at a specified offset (does not change buffer offset)
- Read performs an active read at the current offset and advances it
- TryRead performs tentative reads that only advance the offset if successful
- Checkpoints allow speculative multi-step reads with rollback support
IReadableByteBuffer buffer = ...;
// Reads at a custom offset (does not change buffer offset)
var b1 = buffer.GetByte(customOffset);
// Reads at current offset and advances it by 1
var b2 = buffer.ReadByte();
// Attempts to read without throwing if insufficient data exists
if ( buffer.TryReadInt32( out var value ) )
{
// value successfully read
}
// Speculative read with rollback support
using var checkpoint = buffer.Checkpoint();
try
{
var b3 = buffer.ReadByte();
var i1 = buffer.ReadInt32();
// Commit the checkpoint to make the offset changes permanent
checkpoint.Commit();
}
catch ( ArgumentOutOfRangeException )
{
// Rollback to the checkpointed offset if not committed
}
Renting Buffers from a Pool
For scenarios that create many temporary writable buffers (for example when encoding messages), the library provides ByteBufferPool.
The pool rents buffers backed by reusable byte[] instances, reducing allocations and GC pressure.
Example:
var pool = new ByteBufferPool();
using var buffer = pool.Rent();
buffer.WriteInt32( 123 );
buffer.WriteByte( 1 );
var readable = buffer.AsReadableView();
Buffers rented from the pool must be disposed when no longer needed so the underlying byte array can be returned to the pool.
You can also request a specific minimum capacity:
using var buffer = pool.Rent( 4096 );
The returned buffer behaves exactly like a regular WritableByteBuffer. If additional capacity is required while writing, the buffer will automatically grow as needed, just like a regular writable buffer. When this happens, the previously rented array is returned to the pool and a larger one is rented.
When disposed:
- the underlying byte array is returned to the pool
- the buffer instance becomes unusable
Tracked Buffer Pool
For scenarios where many buffers are rented within a defined scope, the library also provides TrackedByteBufferPool.
This pool tracks all rented buffers and automatically disposes any that were not manually released when the pool itself is disposed.
Example:
using var pool = new TrackedByteBufferPool();
var buffer1 = pool.Rent();
var buffer2 = pool.Rent();
buffer1.WriteInt32( 123 );
// disposing the pool automatically disposes any remaining buffers
This can be useful when buffers are created throughout a workflow and manual disposal could be easily forgotten.
Rented buffers can be disposed manually. The tracked pool still keeps the reference until the scope ends, but disposal is idempotent.
Buffer Views (Zero-Copy)
Buffers in v2.x support windowed views, allowing efficient access to subsets of data without copying.
Windowed Readable Buffers
ReadableByteBuffer can represent:
- The entire underlying
byte[]or - A windowed view over a portion of a
byte[]
Windowed views are also created internally when using:
GetByteBuffer( offset, length )ReadByteBuffer( length )
These methods do not allocate.
Instead, they create a readable view over the same underlying array.
This significantly reduces allocations in high-throughput scenarios.
A windowed view shares the underlying array.
If you need to persist the data beyond the lifetime of the original buffer,
call ToArray() to create a copy.
Readable Views over Writable Buffers
IWritableByteBuffer.AsReadableView() creates a readable view over the currently written portion of the writable buffer.
- No allocation occurs.
- The returned buffer shares the underlying array.
- The readable view is valid as long as the writable buffer is not modified or compacted.
Example:
var writable = new WritableByteBuffer();
writable.WriteInt32( 123 );
var readable = writable.AsReadableView(); // zero-copy
The readable view reflects the writable buffer’s current contents. If the writable buffer is modified, compacted, or resized, the view may no longer represent the same logical data.
If a stable snapshot is required, create a copy:
var snapshot = readable.ToArray();
Writable Views
WritableByteBuffer.CreateView( offset ) creates a windowed writable view starting at the specified offset. They allow writing to a specific portion of the buffer without affecting the main write cursor.
The returned view:
- Shares the same underlying memory as the parent buffer (zero-copy)
- Allows overwriting data within the already written portion
- Cannot grow the parent buffer
- Cannot write beyond the already written portion of the parent buffer at the time of view creation
- Maintains its own write cursor independent from the parent
Example:
var buffer = new WritableByteBuffer();
buffer.WriteBytes( [1, 2, 3, 4, 5] );
// create a writable view starting at offset 2
// writing to the view modifies the parent buffer at the corresponding positions
var view = buffer.CreateView( 2 )
.WriteBytes( [9, 9] );
// buffer now contains: [1, 2, 9, 9, 5]
Writable views are limited to the portion of the buffer that existed when the view was created.
If the parent buffer grows after the view is created, the view cannot access or write into the new region.
Views should not be used after the parent buffer has been modified or compacted, as their behavior becomes undefined.
Writable views do not support operations that would alter the parent buffer’s structure (such as Clear, Compact or Rebase).
ToArray() Behavior
ToArray() on a readable buffer returns:
- The backing array if the readable buffer owns it entirely
- A copy if the readable buffer is a windowed view
This ensures:
- No unnecessary allocations for full buffers
- Safe behavior for windowed or writable-backed views
On writable buffers, ToArray() always creates a copy of the currently written portion, as writable buffers do not guarantee stable underlying data.
Summary of Allocation Behavior
| Operation | Allocates? |
|---|---|
GetByteBuffer |
❌ No |
ReadByteBuffer |
❌ No |
AsReadableView |
❌ No |
ToArray() (full-owned buffer) |
❌ No |
ToArray() (windowed view) |
✅ Yes |
These improvements significantly reduce allocations in decoding pipelines and high-throughput scenarios while maintaining safety when snapshots are required.
Good to know...
ReadableBytesexposes how many bytes remain unread.ResetOffsetresets the read offset (undoes reads).ToArrayreturns the entire buffer, regardless of offset.- Except for reading/getting methods, the API follows a fluent design.
IWritableByteBufferis not expected in middleware.
Output Pipeline Notes
While middleware in the input pipeline is expected to use IReadableByteBuffer, the output pipeline is more permissive.
In the output pipeline:
byte[]IReadableByteBufferIByteBufferIWritableByteBuffer
are all supported and ultimately treated as byte[] before being written to the transport.
This allows adapters and handlers in the output pipeline to work with writable buffers when building outgoing payloads, while preserving the strict readable-only design in the input pipeline.
Migrating from v1.x
In v1.x, a single IByteBuffer interface handled both reading and writing. Consumers had to:
- Check readability/writability
- Or handle runtime exceptions
In v2.x:
IByteBuffernow contains only common members.IReadableByteBufferandIWritableByteBufferinherit from it.- Read and write responsibilities are clearly separated.
Migration Guidelines
- Replace
IByteBufferwith: IReadableByteBufferfor reading scenariosIWritableByteBufferfor writing scenarios- Most APIs remain the same, so migration is typically straightforward.
- Middleware should use
IReadableByteBuffer. IWritableByteBufferis not expected in middleware.- Automatic conversion from
byte[]toIWritableByteBufferis not supported.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Faactory.Channels.Buffers:
| Package | Downloads |
|---|---|
|
Faactory.Channels
Channels |
|
|
Faactory.Channels.Abstractions
Channels |
|
|
Faactory.Channels.Core
Channels |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.8.2 | 76 | 6/4/2026 |
| 2.8.1 | 192 | 5/29/2026 |
| 2.8.0 | 177 | 5/29/2026 |
| 2.7.0 | 164 | 5/28/2026 |
| 2.6.0 | 190 | 5/27/2026 |
| 2.5.2 | 219 | 5/25/2026 |
| 2.5.1 | 187 | 5/21/2026 |
| 2.5.0 | 159 | 5/20/2026 |
| 2.4.0 | 191 | 5/19/2026 |
| 2.3.0 | 233 | 3/6/2026 |
| 2.2.1 | 157 | 3/6/2026 |
| 2.2.0 | 159 | 3/5/2026 |
| 2.1.0 | 228 | 2/25/2026 |
| 2.0.0 | 186 | 2/23/2026 |
| 2.0.0-preview.10 | 63 | 2/23/2026 |
| 2.0.0-preview.9 | 62 | 2/23/2026 |
| 2.0.0-preview.8 | 62 | 2/23/2026 |
| 2.0.0-preview.7 | 66 | 2/23/2026 |
| 2.0.0-preview.6 | 68 | 2/20/2026 |
| 2.0.0-preview.5 | 70 | 2/20/2026 |