Thread-safe in-memory cache for .NET — LRU/LFU eviction, TTL expiration, tag-based invalidation, cache warming, and configurable max size.
dotnet add package Philiprehberger.CacheKitThread-safe in-memory cache for .NET — LRU/LFU eviction, TTL expiration, tag-based invalidation, cache warming, and configurable max size.
dotnet add package Philiprehberger.CacheKit
using Philiprehberger.CacheKit;
// Create a cache
var cache = new Cache<string>(maxSize: 100, defaultTtl: TimeSpan.FromMinutes(5));
// Set/Get
cache.Set("user:1", "Alice");
var name = cache.Get("user:1"); // "Alice"
// TryGet pattern
if (cache.TryGet("user:1", out var value))
Console.WriteLine(value);
// TTL per entry
cache.Set("session", "abc123", ttl: TimeSpan.FromMinutes(30));
// Tags for group invalidation
cache.Set("post:1", "Hello", tags: new[] { "posts", "user:1" });
cache.Set("post:2", "World", tags: new[] { "posts", "user:1" });
cache.InvalidateByTag("user:1"); // removes both
Enable periodic removal of expired entries to proactively free memory instead of waiting for access-triggered eviction:
using var cache = new Cache<string>(new CacheOptions
{
MaxSize = 10000,
DefaultTtl = TimeSpan.FromMinutes(5),
BackgroundCleanupInterval = TimeSpan.FromMinutes(1)
});
cache.Set("session:1", "data");
// Expired entries are automatically removed every minute
// Dispose the cache to stop the background timer
Set a memory budget and provide size hints to evict LRU entries when the budget is exceeded:
using var cache = new Cache<byte[]>(new CacheOptions
{
MaxSize = 10000,
MaxMemoryBytes = 50 * 1024 * 1024 // 50 MB
});
cache.Set("image:1", imageBytes, estimatedSize: imageBytes.Length);
cache.Set("image:2", otherBytes, estimatedSize: otherBytes.Length);
// When total estimated size exceeds 50 MB, LRU entries are evicted
var stats = cache.Stats;
Console.WriteLine($"Current memory usage: {stats.CurrentEstimatedSize} bytes");
Track hits, misses, and evictions for entries grouped by tag:
var cache = new Cache<string>(maxSize: 1000);
cache.Set("user:1", "Alice", tags: new[] { "users" });
cache.Set("user:2", "Bob", tags: new[] { "users" });
cache.Get("user:1");
cache.Get("user:2");
var tagStats = cache.GetTagStatistics("users");
Console.WriteLine($"Tag 'users' — Hits: {tagStats.Hits}, Misses: {tagStats.Misses}, Evictions: {tagStats.Evictions}");
Use GetOrSetAsync to populate cache entries from async sources like databases or HTTP calls:
var user = await cache.GetOrSetAsync(
"user:42",
async () => await httpClient.GetFromJsonAsync<string>("/api/users/42"),
ttl: TimeSpan.FromMinutes(5),
tags: new[] { "users" }
);
Retrieve multiple entries at once with GetMany. Missing or expired keys are omitted:
var keys = new[] { "user:1", "user:2", "user:3" };
var found = cache.GetMany(keys);
// found is Dictionary<string, string> with only the keys that exist
Pre-load entries from a data source on startup using WarmAsync:
var cache = new Cache<string>(maxSize: 10000);
var loaded = await cache.WarmAsync<string>(async () =>
{
var users = await db.GetAllUsersAsync();
return users.Select(u => new KeyValuePair<string, string>($"user:{u.Id}", u.Name));
}, ttl: TimeSpan.FromMinutes(30), tags: new[] { "users" });
Console.WriteLine($"Warmed {loaded} entries");
Switch from the default LRU to Least Frequently Used eviction, which tracks access counts per key and evicts the entry accessed the fewest times:
var cache = new Cache<string>(new CacheOptions
{
MaxSize = 1000,
EvictionPolicy = EvictionPolicy.Lfu
});
cache.Set("hot", "frequently-accessed");
cache.Set("cold", "rarely-accessed");
// Access "hot" many times
for (var i = 0; i < 100; i++)
cache.Get("hot");
// When capacity is reached, "cold" is evicted first (fewer accesses)
Register a callback that fires only when a key expires due to TTL (not capacity eviction):
cache.OnExpired((key, value) =>
{
Console.WriteLine($"TTL expired: {key}");
// Refresh from database, log metric, etc.
});
// OnEvict still fires for all removals (capacity, TTL, tag, predicate)
cache.OnEvict((key, value) =>
{
Console.WriteLine($"Removed: {key}");
});
Register a callback to be notified when entries are evicted:
cache.OnEvict((key, value) =>
{
Console.WriteLine($"Evicted: {key} = {value}");
});
Cache<V>| Method | Description |
|---|---|
Set(key, value, ttl?, tags?, estimatedSize?) | Store a value with optional TTL, tags, and size hint |
Get(key) | Get value or default if missing/expired |
TryGet(key, out value) | Try to get value, returns bool without throwing |
Has(key) | Check if key exists and is not expired |
Delete(key) | Remove a key |
InvalidateByTag(tag) | Remove all entries with a given tag |
Keys() | Get all non-expired keys |
Clear() | Remove all entries |
GetOrSet(key, factory, ttl?) | Get value or create it atomically using factory |
GetOrSetAsync(key, factory, ttl?, tags?) | Async version of GetOrSet with optional tags |
GetMany(keys) | Get multiple values; missing keys omitted from result |
OnEvict(callback) | Register callback for eviction notifications |
OnExpired(callback) | Register callback for TTL expiration events |
WarmAsync<T>(loader, ttl?, tags?) | Pre-load entries from an async data source |
DeleteWhere(predicate) | Remove all entries matching predicate, returns count |
GetTagStatistics(tag) | Get per-tag hits, misses, and eviction counts |
Stats | Get cache statistics (hits, misses, evictions, current estimated size, hit rate) |
Size | Current number of entries |
Dispose() | Stop background cleanup timer and release resources |
CacheOptions| Property | Type | Default | Description |
|---|---|---|---|
MaxSize | int | 1000 | Maximum number of entries before LRU eviction |
DefaultTtl | TimeSpan? | null | Default time-to-live for entries without explicit TTL |
BackgroundCleanupInterval | TimeSpan? | null | Interval for background expired-entry cleanup |
MaxMemoryBytes | long? | null | Memory budget in bytes for size-based eviction |
EvictionPolicy | EvictionPolicy | Lru | Eviction strategy: Lru or Lfu |
CacheStats| Property | Type | Description |
|---|---|---|
Hits | long | Total cache hits |
Misses | long | Total cache misses |
Evictions | long | Total entries evicted |
CurrentEstimatedSize | long | Total estimated size of all entries in bytes |
HitRate | double | Hit rate between 0.0 and 1.0 |
TagStats| Property | Type | Description |
|---|---|---|
Hits | long | Hits for entries with this tag |
Misses | long | Misses for entries with this tag |
Evictions | long | Evictions for entries with this tag |
dotnet build src/Philiprehberger.CacheKit.csproj --configuration Release
If you find this project useful: