Skip to main content

Quick start

An object pool needs a reference type and a struct policy. Create() handles a miss; TryReset() prepares an object before retention.

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;
}
}

Return transfers ownership to the pool. The reset runs before the object becomes available to another renter.

Scoped lease

When a rental does not cross an await, a stack-only lease can guarantee the return:

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

Or expose the value directly:

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

On .NET 10, prefer the out overload for performance-critical synchronous paths. It avoids repeated lease.Value ownership validation while keeping automatic return. On .NET 8, manual Rent/Return remains faster.

PooledLease is a ref struct; it cannot be captured, boxed, stored on the heap, or carried across an await. Use manual Rent/Return with try/finally for asynchronous ownership.

Shared collection pool

Specialized pools return empty collections and discard collections whose backing storage grew beyond the configured limit:

List<int> numbers = ListPool<int>.Shared.Rent();

try
{
numbers.Add(1);
numbers.Add(2);
Consume(numbers);
}
finally
{
ListPool<int>.Shared.Return(numbers);
}

Next: read the ownership rules, then choose a pool from the API guide.