Complete reference for all public types, methods, and properties in XpressCache.
- ICacheStore Interface
- CacheStore Class
- CacheStoreOptions Class
- CacheLoadBehavior Enum
- CacheValidationContext Struct
The primary interface for cache operations.
Namespace: XpressCache
bool EnableCache { get; set; }Gets or sets whether the cache is enabled.
Remarks:
- When
false, all cache operations become no-ops LoadItemwill always invoke the recovery functionSetItemand other operations will do nothing- Changing this property clears all cached items
Example:
// Temporarily disable caching
cache.EnableCache = false;
var result = await cache.LoadItem(...); // Always loads from source
// Re-enable
cache.EnableCache = true; // Clears cache firstValueTask<T?> LoadItem<T>(
Guid? entityId,
string? subject,
Func<Guid, Task<T>>? cacheMissRecovery,
Func<T, bool>? syncValidate = null,
Func<T, Task<bool>>? asyncValidate = null,
CacheLoadBehavior behavior = CacheLoadBehavior.Default
) where T : classLoads an item from the cache, or retrieves it using the recovery function if not cached.
Type Parameters:
T- The type of the cached item (must be a reference type)
Parameters:
entityId- The unique identifier of the entity (required, cannot beGuid.Empty)subject- Optional subject for categorization (null normalized to empty string)cacheMissRecovery- Function to retrieve the item if not in cache (can be null for cache-only checks)syncValidate- Optional synchronous validation function for cached items (returns false to invalidate). Executes in lock-free fast path for optimal performance.asyncValidate- Optional asynchronous validation function for cached items. Use only when validation requires async operations.behavior- Cache loading behavior for stampede prevention (default: uses store-wide setting)
Returns:
- The cached item, recovered item, or
default(T)if not found and no recovery function
Remarks:
- Returns
ValueTask<T?>to avoid Task allocation on cache hits - Implements double-check locking when stampede prevention is active
- Expired entries are automatically removed
- Entry expiration is renewed on cache hit (best-effort)
- When both validators are provided,
syncValidateruns first
Example:
// Basic usage
var user = await cache.LoadItem<User>(
entityId: userId,
subject: "users",
cacheMissRecovery: async (id) => await database.GetUserAsync(id)
);
// With synchronous validation (preferred for performance)
var product = await cache.LoadItem<Product>(
entityId: productId,
subject: "products",
cacheMissRecovery: LoadProductAsync,
syncValidate: (p) => p.IsValid
);
// With async validation (when needed)
var product = await cache.LoadItem<Product>(
entityId: productId,
subject: "products",
cacheMissRecovery: LoadProductAsync,
asyncValidate: async (p) => await IsProductStillValidAsync(p)
);
// Force stampede prevention
var report = await cache.LoadItem<Report>(
entityId: reportId,
subject: "reports",
cacheMissRecovery: GenerateReportAsync,
behavior: CacheLoadBehavior.PreventStampede
);
// Cache-only check (no recovery)
var cached = await cache.LoadItem<User>(userId, "users", cacheMissRecovery: null);
if (cached == null)
{
// Not in cache
}ValueTask<T?> LoadItem<T>(
Guid? entityId,
string? subject,
Func<Guid, Task<T>>? cacheMissRecovery,
Func<T, CacheValidationContext, bool>? syncValidateWithContext = null,
Func<T, CacheValidationContext, Task<bool>>? asyncValidateWithContext = null,
CacheLoadBehavior behavior = CacheLoadBehavior.Default
) where T : classLoads an item from the cache with timing context available for validation.
Type Parameters:
T- The type of the cached item (must be a reference type)
Parameters:
entityId- The unique identifier of the entitysubject- Optional subject for categorizationcacheMissRecovery- Function to retrieve the item if not in cachesyncValidateWithContext- Synchronous validation function that receivesCacheValidationContextwith timing informationasyncValidateWithContext- Asynchronous validation function with timing contextbehavior- Cache loading behavior for stampede prevention
Returns:
- The cached item, recovered item, or
default(T)if not found
Remarks: This overload provides timing context to validation callbacks, enabling sophisticated time-based validation logic such as:
- Proactive refresh when entries are near expiration
- Invalidating entries based on age rather than just TTL
- Implementing custom staleness policies
Example:
// Proactive refresh at 75% TTL elapsed
var user = await cache.LoadItem<User>(
userId, "users", LoadUserAsync,
syncValidateWithContext: (user, ctx) => ctx.ExpiryProgress < 0.75
);
// Refresh items with less than 30 seconds remaining
var data = await cache.LoadItem<Data>(
dataId, "data", LoadDataAsync,
syncValidateWithContext: (data, ctx) => ctx.TimeToExpiry.TotalSeconds > 30
);
// Refresh items older than 2 minutes regardless of TTL
var config = await cache.LoadItem<Config>(
configId, "config", LoadConfigAsync,
syncValidateWithContext: (config, ctx) => ctx.Age.TotalMinutes < 2
);
// Async validation with timing context
var data = await cache.LoadItem<Data>(
dataId, "data", LoadDataAsync,
asyncValidateWithContext: async (data, ctx) =>
{
// Proactively refresh if near expiry and data is stale
if (ctx.ExpiryProgress > 0.8)
return await IsDataFreshAsync(data);
return true;
}
);Task SetItem<T>(Guid? entityId, string? subject, T item) where T : classStores an item in the cache.
Type Parameters:
T- The type of the item to cache
Parameters:
entityId- The unique identifier of the entitysubject- Optional subject for categorizationitem- The item to cache
Returns:
- A completed task
Remarks:
- If an item with the same key exists, it is replaced
- Does nothing if
EnableCacheis false - Does nothing if
entityIdis null orGuid.Empty
Example:
var user = new User { Id = userId, Name = "John" };
await cache.SetItem(userId, "users", user);bool RemoveItem<T>(Guid? entityId, string? subject)Removes a specific item from the cache.
Type Parameters:
T- The type of the item to remove
Parameters:
entityId- The unique identifier of the entitysubject- The subject used when storing the item
Returns:
trueif the item was found and removed; otherwisefalse
Example:
bool removed = cache.RemoveItem<User>(userId, "users");
if (removed)
{
Console.WriteLine("User removed from cache");
}List<T>? GetCachedItems<T>(string? subject) where T : classGets all cached items of a specific type and subject.
Type Parameters:
T- The type of items to retrieve
Parameters:
subject- The subject to filter by
Returns:
- A list of all matching cached items, or
nullif cache is disabled
Remarks:
- Performance: O(n) operation where n is the total cache size
- Expired entries encountered during scan are removed
- Use sparingly in performance-critical code
- Consider secondary indices for frequent use
Example:
// Get all cached users
var allUsers = cache.GetCachedItems<User>("users");
if (allUsers != null)
{
Console.WriteLine($"Found {allUsers.Count} cached users");
}void Clear()Removes all cached items.
Example:
cache.Clear();
Console.WriteLine("Cache cleared");void CleanupCache()Removes expired items from the cache.
Remarks:
- Performs a full scan of the cache
- Should be called periodically (e.g., via background timer)
- The cache also performs lazy cleanup during access operations
- Not strictly necessary to call frequently due to lazy cleanup
Example:
// Setup periodic cleanup
var timer = new Timer(_ => cache.CleanupCache(), null,
TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));The default implementation of ICacheStore.
Namespace: XpressCache
public CacheStore(ILogger<CacheStore> logger)Initializes a new instance with default options.
Parameters:
logger- Logger for diagnostic output (required)
Example:
var cache = new CacheStore(loggerFactory.CreateLogger<CacheStore>());public CacheStore(
ILogger<CacheStore> logger,
IOptions<CacheStoreOptions>? options
)Initializes a new instance with configuration from dependency injection.
Parameters:
logger- Logger for diagnostic output (required)options- Configuration options wrapper (if null, uses defaults)
Example:
// In DI container
services.Configure<CacheStoreOptions>(config =>
{
config.DefaultTtlMs = 15 * 60 * 1000;
});
services.AddSingleton<ICacheStore, CacheStore>();Configuration options for the cache store.
Namespace: XpressCache
public bool PreventCacheStampedeByDefault { get; init; } = true;Gets or sets whether cache stampede prevention is enabled by default.
Default: true
Remarks:
- When
true, uses per-key single-flight locking - Can be overridden per-call with
CacheLoadBehavior - Set to
falseto allow parallel cache-miss recovery by default
Example:
var options = new CacheStoreOptions
{
PreventCacheStampedeByDefault = true
};public long DefaultTtlMs { get; init; } = 10 * 60 * 1000;Gets or sets the default time-to-live for cache entries in milliseconds.
Default: 600000 (10 minutes)
Remarks:
- Entries automatically expire after this duration
- Accessing an entry renews its TTL (sliding expiration)
- Must be positive
Example:
var options = new CacheStoreOptions
{
DefaultTtlMs = 30 * 60 * 1000 // 30 minutes
};public int InitialCapacity { get; init; } = 256;Gets or sets the initial capacity of the cache dictionary.
Default: 256
Remarks:
- Higher capacity reduces rehashing but uses more memory upfront
- Set based on expected cache size
- Dictionary grows automatically if exceeded
Example:
var options = new CacheStoreOptions
{
InitialCapacity = 1024 // Expect ~1000 cached items
};public int ProbabilisticCleanupThreshold { get; init; } = 1000;Gets or sets the threshold for triggering probabilistic cleanup during read operations.
Default: 1000
Remarks:
- When cache size exceeds this threshold, cleanup may trigger on reads
- Prevents unbounded cache growth
- Set higher for larger expected cache sizes
Example:
var options = new CacheStoreOptions
{
ProbabilisticCleanupThreshold = 5000
};Specifies cache loading behavior for stampede prevention control.
Namespace: XpressCache
Default = 0Uses the store-wide default behavior from CacheStoreOptions.PreventCacheStampedeByDefault.
Recommended for: Most use cases
Example:
var item = await cache.LoadItem<Item>(
entityId: id,
subject: "items",
cacheMissRecovery: LoadItemAsync,
behavior: CacheLoadBehavior.Default // Optional, this is the default
);PreventStampede = 1Forces per-key single-flight locking regardless of store-wide default.
Use for:
- Database queries
- External API calls
- Complex computations
- Rate-limited operations
Example:
var report = await cache.LoadItem<Report>(
entityId: reportId,
subject: "reports",
cacheMissRecovery: async (id) =>
{
// Expensive operation - ensure only one execution per key
return await GenerateComplexReportAsync(id);
},
behavior: CacheLoadBehavior.PreventStampede
);AllowParallelLoad = 2Allows parallel cache-miss recovery executions regardless of store-wide default.
Use for:
- In-memory operations
- Idempotent functions
- Cheap operations where lock overhead is unnecessary
Example:
var config = await cache.LoadItem<Config>(
entityId: configId,
subject: "config",
cacheMissRecovery: async (id) =>
{
// Cheap, idempotent operation
return await LoadConfigFromMemoryAsync(id);
},
behavior: CacheLoadBehavior.AllowParallelLoad
);Provides timing context for cache entry validation.
Namespace: XpressCache
public long ExpiryTicks { get; }The tick count (from Environment.TickCount64) at which the cache entry will expire.
public long CurrentTicks { get; }The current tick count at the time of validation.
public long TtlMs { get; }The configured time-to-live for cache entries in milliseconds.
public TimeSpan TimeToExpiry { get; }Gets the time remaining until the entry expires. Returns TimeSpan.Zero if already expired.
public TimeSpan Age { get; }Gets how long the entry has been in cache (approximate). Note: Due to sliding expiration, this represents time since last renewal, not time since initial creation.
public double ExpiryProgress { get; }Gets the progress toward expiration as a value between 0.0 and 1.0.
Values:
0.0- Entry was just created/renewed0.5- Entry is halfway to expiration1.0- Entry has expired (or is about to)
// Refresh when 75% of TTL has elapsed
syncValidateWithContext: (item, ctx) => ctx.ExpiryProgress < 0.75
// Refresh items older than 1 minute
syncValidateWithContext: (item, ctx) => ctx.Age.TotalMinutes < 1
// Refresh items with less than 30 seconds remaining
syncValidateWithContext: (item, ctx) => ctx.TimeToExpiry.TotalSeconds > 30
// Combine with business logic
asyncValidateWithContext: async (item, ctx) =>
{
// Proactive refresh near expiry
if (ctx.ExpiryProgress > 0.8)
{
return await IsDataFreshAsync(item);
}
return true;
}public class CachedUserRepository : IUserRepository
{
private readonly ICacheStore _cache;
private readonly IUserRepository _inner;
public CachedUserRepository(ICacheStore cache, IUserRepository inner)
{
_cache = cache;
_inner = inner;
}
public async Task<User?> GetByIdAsync(Guid userId)
{
return await _cache.LoadItem<User>(
entityId: userId,
subject: "users",
cacheMissRecovery: _inner.GetByIdAsync
);
}
public async Task UpdateAsync(User user)
{
await _inner.UpdateAsync(user);
// Invalidate cache
_cache.RemoveItem<User>(user.Id, "users");
}
}public async Task<Product?> GetProductWithPriceValidationAsync(Guid productId)
{
return await _cache.LoadItem<Product>(
entityId: productId,
subject: "products",
cacheMissRecovery: LoadProductFromDatabaseAsync,
syncValidate: (cached) => cached.Price == _currentPrices[cached.Id]
);
}public async Task<StockPrice?> GetStockPriceAsync(Guid stockId)
{
return await _cache.LoadItem<StockPrice>(
entityId: stockId,
subject: "prices",
cacheMissRecovery: FetchLatestPriceAsync,
syncValidateWithContext: (price, ctx) =>
{
// Refresh prices that are more than 80% through their TTL
// This provides proactive refresh before expiry
return ctx.ExpiryProgress < 0.8;
}
);
}public class MultiLevelCache
{
private readonly ICacheStore _l1Cache;
private readonly ICacheStore _l2Cache;
public async Task<T?> GetAsync<T>(Guid id, Func<Guid, Task<T>> source)
where T : class
{
// Try L1
var item = await _l1Cache.LoadItem<T>(
id, "l1",
cacheMissRecovery: null
);
if (item != null) return item;
// Try L2, fallback to source
item = await _l2Cache.LoadItem<T>(
id, "l2",
cacheMissRecovery: source
);
// Populate L1
if (item != null)
{
await _l1Cache.SetItem(id, "l1", item);
}
return item;
}
}