Cache types
Tharga.Cache exposes four cache interfaces, each with a different expiration strategy. Inject the one that matches the lifetime your data needs — they share the same ICache operations (see Common operations) and differ only in when items expire.
| Interface | Expiration | Lifetime | Typical use |
|---|---|---|---|
IEternalCache |
Never (until explicitly removed) | Singleton | Reference data that rarely changes |
ITimeToLiveCache |
Fixed time after insertion (TTL) | Singleton | API responses, computed results |
ITimeToIdleCache |
Reset on every access (TTI) | Singleton | Session-like data kept alive while in use |
IScopeCache |
End of the DI scope | Scoped | Per-request memoization |
IEternalCache
Data never expires unless explicitly dropped or invalidated. Registered as a singleton.
public class UserService(IEternalCache cache)
{
public Task<User> GetUserAsync(string userId) =>
cache.GetAsync<User>(userId, () => LoadUserAsync(userId));
}
ITimeToLiveCache
Data expires a fixed time after insertion (TTL). Registered as a singleton.
var data = await ttlCache.GetAsync<Product>(
"product-123",
() => LoadProductAsync(123),
TimeSpan.FromMinutes(10));
ITimeToIdleCache
The expiration clock resets every time the item is accessed (TTI). Useful for session-like data that should stay cached while it is actively being used and expire once it goes quiet.
var session = await ttiCache.GetAsync<SessionData>(
"session-abc",
() => LoadSessionAsync("abc"),
TimeSpan.FromMinutes(30));
IScopeCache
A scoped cache instance, cleared at the end of the DI scope (for example, per HTTP request). Data never expires within the scope, which makes it a clean way to memoize a value that may be requested several times while handling a single request.
var result = await scopeCache.GetAsync<RequestContext>(
"current-context",
() => BuildContextAsync());
Stale-while-revalidate
For the time-based caches, enabling StaleWhileRevalidate returns expired data immediately while fresh data is fetched in the background — eliminating the latency spike of a cache miss.
o.RegisterType<Product, IMemory>(t =>
{
t.StaleWhileRevalidate = true;
t.DefaultFreshSpan = TimeSpan.FromMinutes(5);
});
Use GetWithCallbackAsync to be notified when the fresh value arrives:
var (data, isFresh) = await cache.GetWithCallbackAsync<Product>(
"product-123",
() => LoadProductAsync(123),
async freshData =>
{
// Called when the background refresh completes
await NotifyClientsAsync(freshData);
},
TimeSpan.FromMinutes(5));
if (!isFresh)
{
// data is stale; the callback will fire when fresh data is ready
}
Events
All cache types raise events for observing activity:
cache.DataSetEvent += (sender, args) => Console.WriteLine($"Cached: {args.Key}");
cache.DataGetEvent += (sender, args) => Console.WriteLine($"Retrieved: {args.Key}");
cache.DataDropEvent += (sender, args) => Console.WriteLine($"Removed: {args.Key}");