Introduction
In high-throughput .NET services, allocating and disposing of expensive resources—such as network clients, database connections, cryptographic buffers, or heavy state machines—can quickly trigger garbage collection pressure and latency spikes. While .NET provides basic pooling mechanisms, production workloads often require advanced features like circuit breaking, automatic eviction, multi-tenancy, and built-in observability.
Introducing the Esox.SharpAndRusty.ObjectPool, a thread-safe, generic object pool targeting .NET 8, .NET 9 and .NET 10.
What Makes It Different?
Unlike traditional object pools that rely heavily on throwing exceptions on contention or failures, Esox.SharpAndRusty.ObjectPool is built on a Result-based API using ExtendedResult<T,Error> from the Esox.SharpAndRusty package.
Key Highlights
- Result-Based Error Handling: Operations do not throw unexpected runtime exceptions; you explicitly handle outcomes via functional
.Match()or pattern checking. - Resilience Built-In: Protect factory initializations with an integrated Circuit Breaker to prevent cascading failures.
- Intelligent Eviction & Expiration: Clean up idle or stale resources automatically using TTL, idle timeouts, or custom predicates.
- Pre-Warming: Eliminate cold-start performance penalties by pre-populating pools to a specific count or capacity percentage before serving traffic.
Quick Installation
Add the package to your project via the Nuget manager in your IDE or using the .NET CLI:
dotnet add package Esox.SharpAndRusty.ObjectPool
A Look at the Code
Checking out and returning objects is safe and expressive. Disposing the wrapped model automatically returns the underlying instance to the pool:
var pool = new DynamicObjectPool<HttpClient>(() => new HttpClient());
var result = pool.GetObject();
result.Match(
poolModel =>
{
using (poolModel) // Automatically returned on Dispose()
{
poolModel.Unwrap().GetAsync("https://api.example.com");
}
},
error => Console.WriteLine($"Pool acquisition failed: {error.Message}")
);
Dependency Injection and Resilience Configuration
In ASP.NET Core or worker service, configure your pool fluently inside the Program.cs file:
services.AddDynamicObjectPool<DbConnection>(sp => CreateConnection())
.WithMaxSize(100)
.WithMaxActiveObjects(50)
.WithDefaultTimeout(TimeSpan.FromSeconds(5))
// Circuit Breaker protection
.WithCircuitBreaker(failureThreshold: 5, openDuration: TimeSpan.FromSeconds(30))
// Eviction for stale objects
.WithTimeToLive(TimeSpan.FromMinutes(30))
.WithIdleTimeout(TimeSpan.FromMinutes(5))
// Pre-warm before traffic arrives
.WithAutoWarmupPercentage(targetPercentage: 50)
// OpenTelemetry integration
.WithTelemetry(meterName: "MyApp.Pools");
Lifecyle Hooks for State Management
Need to reset connection state or for example authenticate on creation? We got you covered! Lifecycle hooks cover each stage of an object’s lifecycle:
services.AddObjectPool<DbConnection>(builder => builder
.WithFactory(() => new DbConnection(connectionString))
.WithLifecycleHooks(hooks =>
{
hooks.OnCreateAsync = async conn => await conn.OpenAsync();
hooks.OnReturn = conn => conn.ClearAllPools();
hooks.OnDispose = conn => conn.Close();
hooks.OnEvict = (conn, reason) => logger.LogInformation("Evicted: {Reason}", reason);
}));
Native Health Checks & Open Telemetry
Plug directly into ASP.NET Core health checks and monitor pool utilization with Open Telemetry metrics:
builder.Services
.AddHealthChecks()
.AddObjectPoolHealthCheck<DbConnection>("db-pool", tags: ["ready"]);
Summary
Esox.SharpAndRusty.ObjectPool bridges the gap between raw low-level pooling and modern cloud-native .NET architecture. It equips developers with resilient lifecycle controls, predictable error handling, and end-to-end observability out of the box.
Please note that this is an open source project with one maintainer at this moment (if you feel like it, I would like to welcome more maintainers). This means that it may contain errors and bugs.
If you suggestions or find bugs, please let me know. The source can be found here.




