Reservoir 0.1.35

There is a newer version of this package available.
See the version list below for details.
dotnet add package Reservoir --version 0.1.35
                    
NuGet\Install-Package Reservoir -Version 0.1.35
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Reservoir" Version="0.1.35">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Reservoir" Version="0.1.35" />
                    
Directory.Packages.props
<PackageReference Include="Reservoir">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Reservoir --version 0.1.35
                    
#r "nuget: Reservoir, 0.1.35"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Reservoir@0.1.35
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Reservoir&version=0.1.35
                    
Install as a Cake Addin
#tool nuget:?package=Reservoir&version=0.1.35
                    
Install as a Cake Tool

Reservoir

Reservoir provides bounded, thread-safe object pooling with an allocation-free warm rent/return path.

var pool = new ObjectPool<MyBuffer, MyBufferPolicy>(maxCapacity: 64);
MyBuffer buffer = pool.Rent();

try
{
    // Use buffer.
}
finally
{
    pool.Return(buffer);
}

Implement IPooledObjectPolicy<T> on a struct to let the JIT specialize and inline policy calls. Create() supplies an object when the pool is empty. TryReset() prepares a returned object for reuse; returning false discards it.

Types designed for pooling can implement IResettable and use the constrained built-in policy. This avoids runtime type checks on the rent/return path:

var pool = new ObjectPool<MyResettableBuffer, ResettablePooledObjectPolicy<MyResettableBuffer>>();

Discarded objects are disposed when they implement IDisposable. Clear() drains retained objects and disposes them while leaving the pool usable. Dispose() drains and permanently closes the pool; later returns are disposed immediately and later rents throw ObjectDisposedException.

Returning an object transfers ownership to the pool. Do not access it afterward and never return it twice. Another thread may rent it immediately after Return() completes.

Debug builds detect objects returned twice or returned to the wrong pool and throw InvalidOperationException. They also report rentals that become unreachable without being returned, including the rent-site stack trace, through Trace and ObjectPoolDiagnostics.LeakDetected. Define RESERVOIR_DIAGNOSTICS to enable the same checks in a Release or staging build. Diagnostics are compiled out when neither DEBUG nor RESERVOIR_DIAGNOSTICS is defined, leaving no fields or calls on the Release hot path.

Pools retain at most maxCapacity objects. Default capacity is Math.Max(32, 2 * Environment.ProcessorCount). Size capacity for peak simultaneous holders, not request rate. When callers hold objects across await, use peak in-flight operations rather than processor count.

Use a scoped lease when the rental does not cross an await. Disposing the stack-only lease returns its value automatically:

using var lease = pool.RentScoped();
MyBuffer buffer = lease.Value;
// Use buffer only while lease is alive.

For a direct local without a separate Value access:

using var lease = pool.RentScoped(out MyBuffer buffer);

Benchmarks

Run the full .NET 10 suite in Release mode:

dotnet run -c Release --project benchmarks/Reservoir.Benchmarks

Every warm pool path measured 0 B allocated per operation. Results below used BenchmarkDotNet 0.15.8's ShortRun job on .NET 10.0.10, Windows 11, and an Intel Core i7-12700K. Nanosecond results vary by machine; compare methods within a table.

Core pool

The payload owns a 256-byte buffer. Lower ratio is better; new is the baseline.

Method Mean Ratio Allocated
new 12.67 ns 1.00 304 B
Reservoir 11.83 ns 0.93 0 B
Microsoft.Extensions.ObjectPool 14.56 ns 1.15 0 B
ConcurrentBag<T> pool 39.48 ns 3.12 0 B

Warm pool allocation guarantee

Pool Mean Allocated
ObjectPool 11.52 ns 0 B
ListPool 13.13 ns 0 B
DictionaryPool 12.21 ns 0 B
HashSetPool 15.04 ns 0 B
QueuePool 13.62 ns 0 B
StackPool 13.86 ns 0 B
StringBuilderPool 14.96 ns 0 B

Specialized workloads

Workload Baseline Reservoir Baseline allocated Reservoir allocated
StringBuilder, append 128 chars 25.66 ns 18.12 ns 400 B 0 B
List<int>, 8 items 15.27 ns 32.12 ns 88 B 0 B
List<int>, 128 items 139.23 ns 126.66 ns 568 B 0 B
List<int>, 2,048 items 1,738.18 ns 1,502.78 ns 8,248 B 0 B

The single-thread TLS StringBuilder cache measured 5.52 ns and 0 B, as expected; it trades away cross-thread reuse and bounded shared capacity. Scoped leases measured 12.67 ns versus 10.50 ns for manual rent/return, with 0 B allocated on both paths.

Raw Markdown, CSV, and HTML exports, including 1-32 worker contention results, are checked in under benchmarks/results/20260811-200439.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Reservoir:

Package Downloads
Dekaf

High-performance, pure C# Apache Kafka client library for .NET

Kevlar

Kevlar is a fast, allocation-conscious resilience library for .NET: retries, circuit breakers, timeouts, rate limiting, concurrency limiting, hedging and fallbacks, composed through one fluent Shield API.

Respire

Fast, modern RESP client for Redis, Valkey, KeyDB, and other RESP-compatible servers.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.4.0 14,177 8/13/2026
1.3.0 144 8/13/2026
1.2.3 185 8/13/2026
1.2.0 104 8/13/2026
1.1.2 110 8/12/2026
1.1.0 97 8/12/2026
1.0.1 120 8/12/2026
0.3.5 115 8/12/2026
0.3.3 91 8/12/2026
0.3.0 101 8/12/2026
0.2.0 111 8/11/2026
0.1.43 103 8/11/2026
0.1.35 94 8/11/2026