ZeroAlloc.Cache
1.1.2
dotnet add package ZeroAlloc.Cache --version 1.1.2
NuGet\Install-Package ZeroAlloc.Cache -Version 1.1.2
<PackageReference Include="ZeroAlloc.Cache" Version="1.1.2" />
<PackageVersion Include="ZeroAlloc.Cache" Version="1.1.2" />
<PackageReference Include="ZeroAlloc.Cache" />
paket add ZeroAlloc.Cache --version 1.1.2
#r "nuget: ZeroAlloc.Cache, 1.1.2"
#:package ZeroAlloc.Cache@1.1.2
#addin nuget:?package=ZeroAlloc.Cache&version=1.1.2
#tool nuget:?package=ZeroAlloc.Cache&version=1.1.2
ZeroAlloc.Cache
Source-generated zero-allocation caching proxy from an annotated interface.
Add [Cache] to an interface and a Roslyn source generator emits a proxy class that transparently intercepts every method call, returning a cached result on hit with no heap allocation on the cache-hit path. Backed by IMemoryCache by default, with optional HybridCache (L1 + L2) opt-in per method. AOT-safe.
Quick start
dotnet add package ZeroAlloc.Cache
[Cache(TtlMs = 60_000)]
public interface IProductRepository
{
ValueTask<Product?> GetByIdAsync(int id, CancellationToken ct);
[Cache(TtlMs = 300_000, MaxEntries = 1_000)]
ValueTask<IReadOnlyList<Product>> SearchAsync(string query, CancellationToken ct);
}
// Register — one line wires everything
builder.Services.AddIProductRepositoryCache<ProductRepositoryImpl>();
Inject IProductRepository anywhere — caching is transparent to the caller.
public class ProductsController(IProductRepository repo)
{
public async Task<Product?> Get(int id, CancellationToken ct)
=> await repo.GetByIdAsync(id, ct); // cache hit = zero allocation
}
Performance
L1 (in-process) cache-hit comparison. .NET 10.0.7, i9-12900HK, BenchmarkDotNet v0.15.8.
| Library | Time | Allocated |
|---|---|---|
Raw IMemoryCache.GetOrCreateAsync |
208 ns | 176 B |
| ZA.Cache proxy | 434 ns | 160 B |
| FusionCache | 989 ns | 112 B |
ZA.Cache is 2.3× faster than FusionCache with comparable allocation. The ~2× premium over hand-rolled IMemoryCache.GetOrCreateAsync is the cost of the typed [Cache] attribute abstraction (generated key building + async wrapper) — in exchange you don't write the lookup boilerplate at every call site. FusionCache's overhead comes from carrying L2-cache and stampede-protection infrastructure even when only L1 is configured.
Full methodology + design analysis: docs/performance.md.
Features
| Feature | Notes |
|---|---|
| Zero allocation on cache hit | Key is built at compile time; no boxing, no string interpolation at runtime |
IMemoryCache (default) |
In-process L1 cache; no extra dependencies |
HybridCache (opt-in) |
L1 + L2 distributed cache via Microsoft.Extensions.Caching.Hybrid |
| Method-level override | Any [Cache] on a method shadows the interface-level config for that method |
MaxEntries |
Isolates the method in its own MemoryCache instance with a SizeLimit |
| Compile-time key | Cache key expression is emitted by the generator — zero key-building overhead on hit |
| AOT / trimmer safe | Generated proxy is concrete; no reflection at runtime |
| DI integration | Generated Add{IService}Cache<TImpl>() extension registers everything |
Cache behavior
| Scenario | Behavior |
|---|---|
| Miss | Inner implementation is called; result is stored in cache with the configured TTL; result is returned |
| Hit | Cached value is returned directly; inner implementation is never invoked; no heap allocation |
Telemetry
Each cached method emits both metrics (via Meter("ZeroAlloc.Cache")) and a tracing span (via ActivitySource("ZeroAlloc.Cache")) — no extra package required, plain BCL System.Diagnostics.
Breaking change in 2.0:
Metername renamed from"zeroalloc.cache"to"ZeroAlloc.Cache"for ecosystem consistency with the other ZeroAlloc telemetry packages. Subscribers must update — calls toAddMeter("zeroalloc.cache")will silently stop receiving metrics:-services.AddOpenTelemetry().WithMetrics(m => m.AddMeter("zeroalloc.cache")); +services.AddOpenTelemetry().WithMetrics(m => m.AddMeter("ZeroAlloc.Cache"));
Metrics. Counters tagged with method (the cached method name): cache.hits, cache.misses, cache.evictions, cache.hybrid_calls (factory invocations on the HybridCache path). The cache.lookup_duration_ms histogram records per-lookup latency tagged with cache.method.
Tracing. Each cached method emits a cache.lookup span tagged with:
| Tag | Value | Notes |
|---|---|---|
cache.method |
"Interface.Method" |
Compile-time constant per emitted method |
cache.tier |
"L1" or "L2" |
L1 = in-process MemoryCache; L2 = HybridCache |
cache.hit |
true / false |
L1 only — HybridCache hides per-call hit/miss state, so this tag is omitted on the L2 path |
Subscribe via OpenTelemetry:
services.AddOpenTelemetry()
.WithMetrics(m => m.AddMeter("ZeroAlloc.Cache"))
.WithTracing(t => t.AddSource("ZeroAlloc.Cache"));
Diagnostics
| ID | Severity | Description |
|---|---|---|
| ZC0001 | Warning | Sliding = true combined with UseHybridCache = true — sliding TTL is silently ignored by the distributed (L2) tier |
| ZC0002 | Warning | A cache key parameter is a reference type (excluding string) — ToString() may not produce a stable unique key |
Documentation
Full docs live in docs/:
- Getting Started
- Attribute Reference
- Diagnostics: ZC0001 · ZC0002
License
MIT
| 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
- Microsoft.Extensions.Caching.Hybrid (>= 10.6.0)
- Microsoft.Extensions.Caching.Memory (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
-
net8.0
- Microsoft.Extensions.Caching.Memory (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
-
net9.0
- Microsoft.Extensions.Caching.Hybrid (>= 10.6.0)
- Microsoft.Extensions.Caching.Memory (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on ZeroAlloc.Cache:
| Package | Downloads |
|---|---|
|
ZeroAlloc.Mediator.Cache
Pipeline behavior that caches IRequest<T> responses when the request type carries [Cache]. Zero-alloc key derivation via interpolated strings on value-type requests. |
GitHub repositories
This package is not used by any popular GitHub repositories.