Getting started
Install
dotnet add package Tharga.Cache
The core package caches in memory and has no external dependencies. Add a backend package only when you want a type to persist outside the process — see Persistence backends.
Register
builder.Services.AddCache();
AddCache is idempotent — calling it more than once (for example when several libraries each register cache types) merges the registrations instead of throwing.
The get-or-load pattern
Inject one of the four cache interfaces and call GetAsync with a key and a fetch delegate. The first call runs the delegate and stores the result; subsequent calls within the fresh span return the cached value without invoking the delegate.
public class WeatherService(ITimeToLiveCache cache)
{
public Task<WeatherForecast[]> GetForecastAsync() =>
cache.GetAsync<WeatherForecast[]>(
"weather-forecast",
() => LoadFromApiAsync(),
TimeSpan.FromMinutes(5));
}
Pick the interface that matches the lifetime you need — IEternalCache, ITimeToLiveCache, ITimeToIdleCache, or IScopeCache. See Cache types.
Common operations
Every cache type shares these operations from ICache:
// Get or load
var data = await cache.GetAsync<MyData>("key", () => FetchDataAsync());
// Peek without triggering a load (returns default if not cached)
var cached = await cache.PeekAsync<MyData>("key");
// Manually set a value
await cache.SetAsync<MyData>("key", myData);
// Remove a specific item
await cache.DropAsync<MyData>("key");
// Mark as stale (triggers reload on next access)
await cache.InvalidateAsync<MyData>("key");
Time-based caches (ITimeToLiveCache, ITimeToIdleCache) also take an explicit fresh span:
var data = await timeCache.GetAsync<MyData>("key", () => FetchAsync(), TimeSpan.FromMinutes(5));
await timeCache.SetAsync<MyData>("key", myData, TimeSpan.FromHours(1));
The no-span
SetAsync<T>(key, data)overload requiresDefaultFreshSpanto be configured on the type registration. The explicit-span overload works unconditionally.
Configuration
Per-type options
Use RegisterType to configure behavior for a specific cached type:
builder.Services.AddCache(o =>
{
o.RegisterType<Product, IMemory>(t =>
{
t.DefaultFreshSpan = TimeSpan.FromMinutes(10);
t.StaleWhileRevalidate = true;
t.MaxCount = 1000;
t.MaxSize = Size.MB * 100;
t.EvictionPolicy = EvictionPolicy.LeastRecentlyUsed;
});
});
| Option | Default | Description |
|---|---|---|
DefaultFreshSpan |
null |
Default TTL when not specified per call |
StaleWhileRevalidate |
false |
Return stale data immediately while refreshing in the background |
ReturnDefaultOnFirstLoad |
false |
Return default(T) on first cache miss instead of blocking; factory runs in the background |
MaxCount |
null |
Maximum number of cached items for this type |
MaxSize |
null |
Maximum total size in bytes for this type |
EvictionPolicy |
FirstInFirstOut |
Strategy when MaxCount or MaxSize is exceeded |
Global options
builder.Services.AddCache(o =>
{
o.MaxConcurrentFetchCount = 20; // Max parallel background fetches (default: 10)
o.WatchDogInterval = TimeSpan.FromMinutes(2); // Stale-cleanup interval (default: 60s)
o.Default = new CacheTypeOptions // Defaults applied to all types
{
DefaultFreshSpan = TimeSpan.FromSeconds(30)
};
});
Eviction policies
When MaxCount or MaxSize is exceeded, items are evicted according to the configured policy:
| Policy | Description |
|---|---|
FirstInFirstOut |
Removes the oldest items first (default) |
LeastRecentlyUsed |
Removes items that haven't been accessed recently |
RandomReplacement |
Removes items at random (lowest overhead) |
Size constants
Use the Size helper for readable byte values — Size.KB, Size.MB, Size.GB, Size.TB:
t.MaxSize = Size.MB * 500; // 500 MB
t.MaxSize = Size.GB * 2; // 2 GB
Key building
Cache keys can be simple strings or composed from multiple parts with KeyBuilder:
// Simple string key (implicit conversion)
Key key = "my-cache-key";
// Composite key from multiple parts
var key = KeyBuilder
.Set("userId", userId)
.Set("department", department);
var data = await cache.GetAsync<UserProfile>(key, () => LoadProfileAsync(userId, department));
Next steps
- Cache types — choose the right expiration strategy.
- Persistence backends — persist types to Redis, MongoDB, or disk.
- Monitoring — inspect and manage the cache at runtime.