Reservoir 0.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Reservoir --version 0.2.0
                    
NuGet\Install-Package Reservoir -Version 0.2.0
                    
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.2.0">
  <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.2.0" />
                    
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.2.0
                    
#r "nuget: Reservoir, 0.2.0"
                    
#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.2.0
                    
#: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.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Reservoir&version=0.2.0
                    
Install as a Cake Tool

Reservoir

Reservoir is bounded, thread-safe object pooling for .NET with a 0 B warm rent/return path. It ships as C# source, so the implementation compiles into your assembly: no runtime dependency, dependency conflict, or extra DLL.

dotnet add package Reservoir

Reservoir is a development dependency, so PrivateAssets="all" is automatic. Package source files join your project compilation, Reservoir types are internal by default, and no Reservoir.dll appears in build output. Requires .NET 10 and C# 12 or later.

Full documentation · Quick start · Design notes · Benchmarks

Quick start

Use a struct policy so the JIT can specialize and inline lifecycle calls:

using Reservoir;

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

try
{
    buffer.Write(payload);
}
finally
{
    pool.Return(buffer);
}

sealed class Buffer
{
    public int Length { get; set; }
    public void Write(ReadOnlySpan<byte> value) => Length += value.Length;
}

readonly struct BufferPolicy : IPooledObjectPolicy<Buffer>
{
    public Buffer Create() => new();

    public bool TryReset(Buffer buffer)
    {
        buffer.Length = 0;
        return true;
    }
}

Create() handles a miss. TryReset() prepares a return and may return false to discard it. Destroy() defaults to IDisposable.Dispose() and can be overridden for custom cleanup.

For synchronous scopes, a stack-only lease guarantees return:

using var lease = pool.RentScoped(out Buffer buffer);
buffer.Write(payload);

Use manual try/finally when ownership crosses an await; PooledLease is a ref struct and cannot cross one.

Built-in pools

List<int> values = ListPool<int>.Shared.Rent();
try
{
    values.Add(42);
    Consume(values);
}
finally
{
    ListPool<int>.Shared.Return(values);
}
Pool Purpose Default largest retained capacity
ListPool<T> List<T> 1,024
DictionaryPool<TKey,TValue> Dictionary<TKey,TValue> with optional comparer 1,024
HashSetPool<T> HashSet<T> with optional comparer 1,024
QueuePool<T> Queue<T> 1,024
StackPool<T> Stack<T> 1,024
StringBuilderPool StringBuilder 4,096
CancellationTokenSourcePool Uncanceled timeout/registration sources n/a

Collections arrive empty. Oversized backing stores are discarded rather than trimmed. Each pool has a Shared instance and constructors for custom retained-object and backing-capacity limits.

CancellationTokenSourcePool returns a rental to its originating pool when disposed:

using CancellationTokenSource source = CancellationTokenSourcePool.Shared.Rent();
source.CancelAfter(TimeSpan.FromSeconds(30));
await ProcessAsync(source.Token);

It reuses a source only when TryReset() confirms cancellation never fired. Dispose it exactly once as sole owner, after all token reads and cancellation operations finish. See the complete concurrency rules.

Core API

  • ObjectPool<T,TPolicy>: generic struct-policy fast path.
  • ObjectPool<T>: convenient Func<T> or interface-policy overload.
  • IResettable + ResettablePooledObjectPolicy<T>: reset logic owned by the pooled type.
  • Rent, Return, RentScoped: manual or lexical ownership.
  • Clear: destroy retained objects while keeping a core pool usable.
  • Dispose: drain and permanently close a core pool; later rents throw and later returns are destroyed.

Pools retain at most maxCapacity idle objects. Default retention is Math.Max(32, 2 * Environment.ProcessorCount). This does not throttle concurrent rentals: size it for peak simultaneous holders. Specialized pools also accept maxRetainedCapacity to reject unusually large backing stores.

Ownership rules

Returning an object transfers ownership to the pool. After Return:

  • never touch the object;
  • never return it twice;
  • never return it to a different pool.

Another thread may rent it immediately. Debug builds detect double returns and wrong-pool returns. They also report rentals that become unreachable without return, including the rent-site stack trace, through Trace and ObjectPoolDiagnostics.LeakDetected.

Define RESERVOIR_DIAGNOSTICS to enable those checks in Release or staging builds. When neither it nor DEBUG is defined, diagnostic fields and calls are compiled out of the hot path.

Define RESERVOIR_PUBLIC when Reservoir types must appear in your assembly's public API:

<PropertyGroup>
  <DefineConstants>$(DefineConstants);RESERVOIR_PUBLIC</DefineConstants>
</PropertyGroup>

Benchmarks

BenchmarkDotNet 0.15.8 ShortRun, .NET 10.0.10, Windows 11, Intel Core i7-12700K:

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

Every measured warm Reservoir path allocated 0 B. Run the suite:

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

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

Why Reservoir

The core uses a fixed, bounded slot array, per-thread stripe affinity, atomic exchange/compare-exchange operations, and cache-line-spaced logical slots. It has no global lock and allocates no nodes on a warm return. Struct policies expose concrete lifecycle calls to generic specialization.

Use ArrayPool<T> for raw arrays. Use Microsoft.Extensions.ObjectPool when ecosystem integration and a normal runtime dependency matter more. Use Reservoir when source ownership, bounded custom-object reuse, struct-policy specialization, scoped leases, and debug ownership diagnostics fit the application.

Reservoir is available under the MIT license.

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 12,913 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