Swap cache providers through config, not a code change
ICacheService is provider-agnostic with a safe-off NoOp default. Application code never changes when the active provider does — only the configuration value does.
$ dotnet add package PowerCSharp.Feature.CacheOne contract, whether the value is a hit, a miss, or null
Sync and async members, stampede protection via GetOrCreate/GetOrCreateAsync, and metadata access via GetMetadata. CacheResult<T> is a readonly struct with Hit, Value, and Metadata — deliberately disambiguating 'not found' from 'found but null'.
var cache = app.Services.GetRequiredService<ICacheService>();
await cache.SetAsync("key", myObject, TimeSpan.FromMinutes(5));
var result = await cache.GetAsync<MyObject>("key");
if (result.Hit)
Console.WriteLine(result.Value);Off by default, safe when it's off
Layer 1 (build-time): don't reference a provider package and its third-party dependency is absent from your dependency tree entirely. Layer 2 (runtime flag): reference it but leave the flag off, and a NoOp implementation registers via TryAddSingleton so dependents still resolve.
Provider selection
PowerFeatures:Cache:Provider
"BitFaster" | "Disk" | "None"
BitFaster
in-memory LRU, requires Feature.Cache.BitFaster
Disk
cross-process LRU, requires Feature.Cache.Disk
NoOp
safe default — always resolves, does nothing
Stampede protection without extra code
Concurrent callers requesting the same missing key trigger exactly one factory call — the rest wait for the result instead of duplicating work against your database or upstream API.
public class ProductService(ICacheService cache)
{
public async Task<Product> GetProductAsync(string sku)
{
var result = await cache.GetAsync<Product>(sku);
if (result.Hit)
return result.Value!;
var product = await FetchFromCatalogAsync(sku);
await cache.SetAsync(sku, product, TimeSpan.FromMinutes(10));
return product;
}
}BitFaster vs. Disk
Both implement the identical ICacheService contract — pick based on where the data should live, not how your code calls it.
PowerCSharp.Feature.Cache.BitFaster
In-memory LRU
- Backed by BitFaster.Caching's ConcurrentLru<K,V> (W-TinyLFU-variant eviction)
- Lock-free concurrent reads; atomic GetOrAdd gives true stampede protection
- The only package in the family with a third-party NuGet dependency — fully isolated to it
- Best for high read-throughput, per-process caching where the dataset fits in RAM
PowerCSharp.Feature.Cache.Disk
Cross-process LRU
- Zero third-party NuGet dependencies — pure System.IO / System.Text.Json
- Atomic writes via write-to-temp-then-rename; per-key .lock files for cross-process coordination
- Sidecar .meta files store metadata separately, readable without deserializing the value
- Best for datasets larger than RAM, or multiple processes sharing a cache on one host
Install the provider you need
$ dotnet add package PowerCSharp.Feature.Cache.BitFaster$ dotnet add package PowerCSharp.Feature.Cache.Disk