CacheUtility 1.1.0
dotnet add package CacheUtility --version 1.1.0
NuGet\Install-Package CacheUtility -Version 1.1.0
<PackageReference Include="CacheUtility" Version="1.1.0" />
<PackageVersion Include="CacheUtility" Version="1.1.0" />
<PackageReference Include="CacheUtility" />
paket add CacheUtility --version 1.1.0
#r "nuget: CacheUtility, 1.1.0"
#:package CacheUtility@1.1.0
#addin nuget:?package=CacheUtility&version=1.1.0
#tool nuget:?package=CacheUtility&version=1.1.0
CacheUtility
A thread-safe, generic wrapper for System.Runtime.Caching that simplifies cache access and supports powerful caching patterns with persistent storage capabilities.
Overview
CacheUtility provides an easy-to-use abstraction over the standard .NET memory cache with additional features:
- Automatic cache population with custom populate methods
- Various expiration strategies (sliding and absolute)
- Thread-safe operations with minimal lock contention
- Support for cache groups for organized data management
- Dependency relationships between cache groups
- Automatic background refresh functionality for non-blocking updates
- Persistent cache storage for data that survives application restarts
- Comprehensive metadata and monitoring for cache analysis and debugging
Installation
Install the CacheUtility NuGet package:
Package Manager Console
Install-Package CacheUtility
.NET CLI
dotnet add package CacheUtility
PackageReference
<PackageReference Include="CacheUtility" Version="1.1.0" />
Quick Start
using CacheUtility;
// Simple caching with automatic population
var userData = Cache.Get("user_123", "users", () => GetUserFromDatabase(123));
// Enable persistent cache (optional)
Cache.EnablePersistentCache();
// Cache with auto-refresh every 5 minutes
var config = Cache.Get("app_config", "settings",
TimeSpan.FromHours(1),
() => LoadConfiguration(),
TimeSpan.FromMinutes(5));
Basic usage
Note: All examples assume you have added the using statement:
using CacheUtility;
Simple caching
The most common pattern is to request an item from the cache, providing a function to generate the item if it doesn't exist:
// Basic usage with default 30-minute sliding expiration
var result = Cache.Get("MyKey", "MyGroupName", () =>
{
return MyLongRunningTask();
});
Caching with expiration
// With custom sliding expiration
var result = Cache.Get("MyKey", "MyGroupName", TimeSpan.FromHours(1), () =>
{
return MyLongRunningTask();
});
// With absolute expiration
var result = Cache.Get("MyKey", "MyGroupName", DateTime.Now.AddDays(1), () =>
{
return MyLongRunningTask();
});
Basic examples
Caching user data
// Cache user data with a sliding expiration
var userData = Cache.Get($"User_{userId}", "UserProfiles", TimeSpan.FromMinutes(30), () =>
{
return database.GetUserById(userId);
});
Caching application settings
// Cache application settings with absolute expiration
var settings = Cache.Get("GlobalSettings", "AppConfig", DateTime.Now.AddHours(12), () =>
{
return configurationService.LoadSettings();
});
Async operations
For async operations, you can use the utility with async/await:
var result = await Cache.Get("MyKey", "MyGroupName", async () =>
{
return await MyLongRunningTaskAsync();
});
Cache management
Removing individual items
Remove a specific item from the cache:
Cache.Remove("MyKey", "MyGroupName");
Group operations
Remove an entire group of cached items:
Cache.RemoveGroup("MyGroupName");
Remove multiple groups:
Cache.RemoveGroup("GroupA", "GroupB", "GroupC");
Retrieving all items from a group
Get all cached items that belong to a specific group:
var allItems = Cache.GetAllByGroup("MyGroupName");
// Iterate through all items in the group
foreach (var kvp in allItems)
{
Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");
}
// Access specific items if you know the key
if (allItems.ContainsKey("MySpecificKey"))
{
var specificItem = allItems["MySpecificKey"];
}
Persistent Cache
CacheUtility supports optional persistent caching, where cached data is automatically saved to disk and survives application restarts. This hybrid approach combines the speed of in-memory caching with the persistence of disk storage.
Enabling persistent cache
Enable persistent cache globally with default settings:
// Enable with default options (%LOCALAPPDATA%/CacheUtility/)
Cache.EnablePersistentCache();
// All cache operations now automatically persist to disk
var userData = Cache.Get("userProfile", "users", () => GetUserFromDatabase(userId));
Enable with custom configuration:
// Enable with custom options
Cache.EnablePersistentCache(new PersistentCacheOptions
{
BaseDirectory = @"C:\MyApp\Cache",
MaxFileSize = 50 * 1024 * 1024 // 50MB limit per file
});
How persistent cache works
When persistent cache is enabled:
- Memory-first performance: Cache operations remain fast using in-memory storage
- Automatic persistence: Data is automatically saved to disk in JSON format
- Seamless fallback: If memory cache is cleared, data is loaded from disk automatically
- Transparent operation: All existing cache APIs work exactly the same
File structure
Persistent cache files are stored with a simple naming pattern:
%LOCALAPPDATA%/CacheUtility/
├── users_userProfile_123.cache # Cache data
├── users_userProfile_123.meta # Expiration metadata
├── reports_monthly_2024.cache
├── reports_monthly_2024.meta
└── settings_appConfig.cache
Configuration options
var options = new PersistentCacheOptions
{
BaseDirectory = @"C:\MyApp\Cache", // Custom cache directory
MaxFileSize = 10 * 1024 * 1024 // 10MB max per cached item (0 = no limit)
};
Cache.EnablePersistentCache(options);
Persistent cache management
Check if persistent cache is enabled:
bool isEnabled = Cache.IsPersistentCacheEnabled;
Get current configuration:
var options = Cache.GetPersistentCacheOptions();
if (options != null)
{
Console.WriteLine($"Cache directory: {options.BaseDirectory}");
Console.WriteLine($"Max file size: {options.MaxFileSize} bytes");
}
Get persistent cache statistics:
var stats = Cache.GetPersistentCacheStatistics();
Console.WriteLine($"Cache enabled: {stats.IsEnabled}");
Console.WriteLine($"Cache directory: {stats.BaseDirectory}");
Console.WriteLine($"Total files: {stats.TotalFiles}");
Console.WriteLine($"Cache files: {stats.CacheFiles}");
Console.WriteLine($"Meta files: {stats.MetaFiles}");
Console.WriteLine($"Orphaned files: {stats.OrphanedFiles}");
Console.WriteLine($"Total size: {stats.TotalSizeFormatted}");
Console.WriteLine($"Average file size: {stats.AverageFileSizeFormatted}");
Console.WriteLine($"Largest file: {stats.LargestFileSize:N0} bytes");
Console.WriteLine($"Smallest file: {stats.SmallestFileSize:N0} bytes");
if (stats.OldestFileTime.HasValue)
{
Console.WriteLine($"Oldest file: {stats.OldestFileTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Directory age: {stats.DirectoryAge?.Days} days");
}
if (stats.NewestFileTime.HasValue)
{
Console.WriteLine($"Newest file: {stats.NewestFileTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Last activity: {stats.TimeSinceLastActivity?.TotalMinutes:F1} minutes ago");
}
Manually clean up expired files:
// Manually trigger cleanup of expired persistent cache files
Cache.CleanupExpiredPersistentCache();
Disable persistent cache:
// Disable persistent caching (returns to memory-only)
Cache.DisablePersistentCache();
Enhanced metadata for persistent cache
When persistent cache is enabled, cache metadata includes additional information:
var metadata = Cache.GetAllCacheMetadata();
foreach (var item in metadata)
{
Console.WriteLine($"Key: {item.CacheKey}");
Console.WriteLine($" Is Persisted: {item.IsPersisted}");
if (item.IsPersisted)
{
Console.WriteLine($" File Path: {item.PersistentFilePath}");
Console.WriteLine($" File Size: {item.PersistentFileSize:N0} bytes");
Console.WriteLine($" Last Persisted: {item.LastPersistedTime:yyyy-MM-dd HH:mm:ss}");
}
}
Automatic cleanup
Persistent cache automatically manages file cleanup:
- Background cleanup: Runs every 30 minutes to remove expired files
- Removal operations: Files are deleted when cache items are removed
- Group operations: Removing a cache group also removes all associated files
- Application shutdown: Proper cleanup when the application exits
Use cases for persistent cache
Application restart scenarios:
// Enable persistent cache
Cache.EnablePersistentCache();
// Cache expensive data that should survive restarts
var expensiveData = Cache.Get("dailyReport", "reports", () => GenerateDailyReport());
// After application restart, data is automatically loaded from disk
var sameData = Cache.Get("dailyReport", "reports", () => GenerateDailyReport());
// No need to regenerate - loaded from persistent storage!
Large dataset caching:
// Cache large datasets that might not fit entirely in memory
var bigData = Cache.Get("dataset_2024", "analytics", () => LoadHugeDataset());
Cross-session data:
// Cache user preferences that should persist across sessions
var userPrefs = Cache.Get($"prefs_{userId}", "userdata", () => LoadUserPreferences(userId));
Best practices for persistent cache
- Monitor disk usage: Use
GetPersistentCacheStatistics()
to monitor cache size - Set size limits: Configure
MaxFileSize
to prevent extremely large cache files - Choose appropriate directories: Use application-specific directories for better organization
- Consider data sensitivity: Don't cache sensitive data that shouldn't be stored on disk
- Regular cleanup: Monitor and clean up cache directories in deployment scripts
Cache metadata and monitoring
Get detailed metadata about cached items for monitoring, debugging, or displaying in management interfaces:
// Get metadata for all cached items
var allMetadata = Cache.GetAllCacheMetadata();
foreach (var metadata in allMetadata)
{
Console.WriteLine($"Key: {metadata.CacheKey}");
Console.WriteLine($" Group: {metadata.GroupName}");
Console.WriteLine($" Type: {metadata.DataType}");
Console.WriteLine($" Size: {metadata.EstimatedMemorySize:N0} bytes");
Console.WriteLine($" Last Refresh: {metadata.LastRefreshTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($" Refresh Interval: {metadata.RefreshInterval}");
Console.WriteLine($" Is Refreshing: {metadata.IsRefreshing}");
Console.WriteLine($" Populate Method: {metadata.PopulateMethodName ?? "Unknown"}");
// Auto-refresh information
if (metadata.NextRefreshTime.HasValue)
{
Console.WriteLine($" Next Refresh: {metadata.NextRefreshTime:yyyy-MM-dd HH:mm:ss}");
var timeUntilRefresh = metadata.NextRefreshTime.Value - DateTime.Now;
Console.WriteLine($" Time Until Refresh: {timeUntilRefresh:hh\\:mm\\:ss}");
}
// Persistent cache information
Console.WriteLine($" Persistent Cache Enabled: {metadata.PersistentCacheEnabled}");
if (metadata.IsPersisted)
{
Console.WriteLine($" Persisted to Disk: Yes");
Console.WriteLine($" Cache File: {metadata.PersistentFilePath}");
Console.WriteLine($" Meta File: {metadata.PersistentMetaFilePath}");
Console.WriteLine($" Cache File Size: {metadata.PersistentFileSize:N0} bytes");
Console.WriteLine($" Meta File Size: {metadata.PersistentMetaFileSize:N0} bytes");
Console.WriteLine($" Total Disk Size: {metadata.TotalPersistentSize:N0} bytes");
Console.WriteLine($" Last Persisted: {metadata.LastPersistedTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($" File Age: {metadata.PersistentFileAge?.TotalHours:F1} hours");
}
else if (metadata.PersistentCacheEnabled)
{
Console.WriteLine($" Persisted to Disk: No (not yet saved)");
}
if (metadata.CollectionCount.HasValue)
{
Console.WriteLine($" Items in Collection: {metadata.CollectionCount}");
}
Console.WriteLine(); // Blank line for readability
}
// You can filter the results as needed
var userDataItems = allMetadata.Where(m => m.GroupName == "UserProfiles");
var itemsWithAutoRefresh = allMetadata.Where(m => m.NextRefreshTime.HasValue);
var persistedItems = allMetadata.Where(m => m.IsPersisted);
Available metadata properties
Each CacheItemMetadata
object contains:
- CacheKey: Original cache key (without group prefix)
- GroupName: Cache group name
- DataType: Type name of the cached object
- EstimatedMemorySize: Estimated memory usage in bytes (using JSON serialization)
- LastRefreshTime: When the data was last refreshed
- LastRefreshAttempt: When the last refresh was attempted (regardless of success)
- RefreshInterval: Auto-refresh interval
- IsRefreshing: Whether a refresh operation is currently in progress
- RefreshStartTime: When the current refresh operation started
- CollectionCount: Number of items if the cached object is a collection
- PopulateMethodName: Name of the method used to populate/refresh the cache item
- NextRefreshTime: When the next refresh is scheduled to occur (if auto-refresh is enabled)
- IsPersisted: Whether this item is persisted to disk (when persistent cache is enabled)
- PersistentCacheEnabled: Whether persistent cache is enabled for this item
- PersistentFilePath: File path of the persistent cache file (if persisted)
- PersistentMetaFilePath: File path of the persistent metadata file (if persisted)
- PersistentFileSize: Size of the persistent cache file in bytes (if persisted)
- PersistentMetaFileSize: Size of the persistent metadata file in bytes (if persisted)
- TotalPersistentSize: Combined size of both cache and metadata files in bytes (if persisted)
- LastPersistedTime: When the item was last persisted to disk
- PersistentFileAge: Age of the persistent cache file (time since last write)
Populate method names
The PopulateMethodName
property helps identify which methods are used to populate cache items:
// Direct method reference - shows actual method name
Cache.Get("key1", "group", MyDataService.LoadUserData);
// PopulateMethodName: "MyDataService.LoadUserData"
// Lambda expression - shows indicator
Cache.Get("key2", "group", () => database.GetUser(123));
// PopulateMethodName: "[Lambda/Anonymous]"
// Anonymous method - shows indicator
Cache.Get("key3", "group", delegate() { return "test"; });
// PopulateMethodName: "[Lambda/Anonymous]"
This is particularly useful for:
- Debugging: Identifying which populate methods are being called
- Performance monitoring: Tracking which data sources are being used
- Code analysis: Understanding cache usage patterns across your application
Use cases for metadata
- Monitoring dashboards: Display cache usage, memory consumption, and refresh status
- Debug interfaces: Inspect cache contents and timing information
- Performance analysis: Identify large cached objects or frequently refreshed items
- Administrative tools: Manage cache contents through custom interfaces
- Reporting: Generate cache usage reports and statistics
Global cache operations
Clear the entire cache:
Cache.RemoveAll();
Clear the cache except for specific groups:
Cache.RemoveAllButThese(new List<string> { "CriticalData", "ApplicationSettings" });
Intermediate features
Removing multiple items
Remove multiple items that contain specific strings:
Cache.Remove(new List<string> { "UserProfile", "123" }, "UserData");
// This will remove any cache key containing both "UserProfile" and "123"
Working with multiple cached items
// Cache some user data
Cache.Get("User1", "UserData", () => GetUserInfo(1));
Cache.Get("User2", "UserData", () => GetUserInfo(2));
Cache.Get("User3", "UserData", () => GetUserInfo(3));
// Get all cached items from the group
var allUsers = Cache.GetAllByGroup("UserData");
Console.WriteLine($"Found {allUsers.Count} cached users");
// Process each cached item
foreach (var user in allUsers)
{
Console.WriteLine($"User Key: {user.Key}, Data: {user.Value}");
}
Advanced features
Automatic data refresh
CacheUtility supports automatic background refresh of cached data at specified intervals. This feature ensures your cache stays up-to-date with fresh data while maintaining high performance by serving existing data immediately, even during refresh operations.
Key benefits:
- Non-blocking: Cache calls return immediately with existing data, even when refresh is in progress
- High availability: Your application remains responsive during slow data refresh operations
- Automatic updates: Data stays fresh without manual intervention
- Error resilient: Failed refreshes don't impact cache availability
Basic refresh usage
// Cache data with automatic refresh every 5 minutes
var userData = Cache.Get("user_123", "UserProfiles",
TimeSpan.FromHours(1), // Sliding expiration
() => database.GetUserById(123), // Populate method
refresh: TimeSpan.FromMinutes(5) // Refresh interval
);
Non-blocking behavior example
// Even if GetExpensiveData() takes 10 seconds to execute,
// subsequent cache calls will return immediately with existing data
var expensiveData = Cache.Get("expensive_key", "DataGroup",
TimeSpan.FromMinutes(30),
() => GetExpensiveDataFromAPI(), // Slow operation
refresh: TimeSpan.FromMinutes(2)
);
// This call returns instantly, even if refresh is running in background
var sameData = Cache.Get("expensive_key", "DataGroup",
TimeSpan.FromMinutes(30),
() => GetExpensiveDataFromAPI(),
refresh: TimeSpan.FromMinutes(2)
);
Real-world refresh scenarios
API data caching:
var weatherData = Cache.Get($"weather_{cityId}", "WeatherCache",
TimeSpan.FromHours(2), // Cache for 2 hours max, after the cache item last has been accessed
() => weatherAPI.GetCurrentWeather(cityId),
refresh: TimeSpan.FromMinutes(15) // Refresh every 15 minutes
);
Database result caching:
var reports = Cache.Get("monthly_reports", "Reports",
TimeSpan.FromHours(4),
() => database.GenerateMonthlyReports(), // Expensive query
refresh: TimeSpan.FromHours(1) // Refresh hourly
);
Configuration data:
var config = Cache.Get("app_config", "Configuration",
TimeSpan.FromDays(1),
() => configService.LoadConfiguration(),
refresh: TimeSpan.FromMinutes(30) // Check for config updates every 30 minutes
);
Cache removal callbacks
CacheUtility supports optional removal callbacks that are invoked when cached items are removed from the cache. This is useful for cleanup operations, logging, or triggering dependent actions.
Basic removal callback
var result = Cache.Get("MyKey", "MyGroupName",
DateTime.Now.AddHours(1), // Either Absolute expiration
TimeSpan.FromMinutes(10), // Or Sliding expiration
CacheItemPriority.Default, // Priority
() => MyLongRunningTask(),
removedCallback: (args) => // Optional callback
{
Console.WriteLine($"Cache item removed. Key: {args.CacheItem.Key}, Reason: {args.RemovedReason}");
});
Removal reasons
The callback provides a CacheEntryRemovedArguments
object that contains:
CacheItem
: The cache item that was removedRemovedReason
: The reason for removal (Removed, Expired, Evicted, ChangeMonitorChanged)
Common removal reasons:
Removed
: Item was explicitly removedExpired
: Item expired (absolute or sliding expiration)Evicted
: Item was evicted due to memory pressureChangeMonitorChanged
: Item was removed due to a dependency change
Practical callback examples
Cleanup resources:
var fileData = Cache.Get("FileData", "Files",
TimeSpan.FromMinutes(30),
CacheItemPriority.Default,
() => LoadFileData("myfile.txt"),
removedCallback: (args) =>
{
if (args.CacheItem.Value is IDisposable disposable)
{
disposable.Dispose();
}
});
Trigger dependent operations:
var config = Cache.Get("AppConfig", "Configuration",
DateTime.Now.AddHours(12),
() => LoadConfiguration(),
removedCallback: (args) =>
{
// Refresh dependent services when configuration changes
if (args.RemovedReason == CacheEntryRemovedReason.Expired)
{
RefreshDependentServices();
}
});
Cache dependencies
Set up dependencies between cache groups so that when one group is cleared, its dependent groups are also cleared:
// Set up dependencies
Cache.SetDependencies("ParentGroup", "ChildGroup1", "ChildGroup2");
// Now when ParentGroup is removed, ChildGroup1 and ChildGroup2 will also be removed
Cache.RemoveGroup("ParentGroup");
Cascading cache invalidation
// Set up dependencies
Cache.SetDependencies("UserData", "UserProfiles", "UserPreferences", "UserActivity");
Cache.SetDependencies("UserProfiles", "ProfilePhotos");
// Now when UserData is cleared, all dependent caches are also cleared
Cache.RemoveGroup("UserData");
// This will clear UserData, UserProfiles, ProfilePhotos, UserPreferences, and UserActivity
Best practices
- Group related items: Use meaningful group names to organize related cache items.
- Consider expiration strategies: Choose between sliding expiration (reset on access) and absolute expiration (fixed time) based on your use case.
- Set dependencies: Use cache dependencies to maintain consistency between related data.
- Use short keys: Keep your cache keys concise but descriptive.
- Choose appropriate refresh intervals:
- Balance data freshness needs with system resources
- Use longer intervals for stable data, shorter for rapidly changing data
- Consider the cost of your populate method when setting refresh frequency
- Remember that refresh happens in background, so cache remains available
- Use removal callbacks wisely:
- Use callbacks for cleanup operations (disposing resources, closing connections)
- Consider performance impact - callbacks are executed synchronously
- Avoid heavy operations in callbacks to prevent blocking cache operations
- Use callbacks for logging and monitoring cache behavior
- Configure persistent cache thoughtfully:
- Enable persistent cache for data that should survive application restarts
- Set appropriate
MaxFileSize
limits to prevent extremely large cache files - Monitor disk usage with
GetPersistentCacheStatistics()
- Choose secure, application-specific directories for cache storage
- Don't cache sensitive data that shouldn't be stored on disk
- Consider the JSON serialization overhead for complex objects
Troubleshooting
Common Issues and Solutions
Persistent Cache Not Working
Problem: Persistent cache files are not being created or loaded.
Solutions:
Check if persistent cache is enabled:
if (!Cache.IsPersistentCacheEnabled) { Cache.EnablePersistentCache(); }
Verify directory permissions:
var stats = Cache.GetPersistentCacheStatistics(); Console.WriteLine($"Cache directory: {stats.BaseDirectory}"); // Ensure your application has read/write access to this directory
Check for serialization issues:
- Ensure cached objects are JSON-serializable
- Avoid circular references in objects
- Consider using
[JsonIgnore]
for non-serializable properties
Performance Issues
Problem: Cache operations seem slow.
Solutions:
Check if you're blocking on populate methods:
// Bad - synchronous database call var data = Cache.Get("key", "group", () => database.GetData()); // Better - use async populate methods when available var data = Cache.Get("key", "group", () => GetDataAsync().Result);
Monitor cache hit rates:
var metadata = Cache.GetAllCacheMetadata(); // Analyze refresh patterns and expiration times
Optimize persistent cache settings:
Cache.EnablePersistentCache(new PersistentCacheOptions { BaseDirectory = @"C:\FastSSD\Cache\", // Use fast storage MaxFileSize = 1024 * 1024 // Limit file sizes });
Memory Usage Concerns
Problem: High memory usage from cached data.
Solutions:
Monitor cache size:
var metadata = Cache.GetAllCacheMetadata(); var totalSize = metadata.Sum(m => m.EstimatedMemorySize); Console.WriteLine($"Total cache size: {totalSize:N0} bytes");
Use appropriate expiration strategies:
// Use sliding expiration for frequently accessed data Cache.Get("key", "group", TimeSpan.FromMinutes(30), () => GetData()); // Use absolute expiration for time-sensitive data Cache.Get("key", "group", DateTime.Now.AddHours(1), () => GetData());
Remove unused cache groups:
Cache.RemoveGroup("unused_group");
Orphaned Files
Problem: Persistent cache directory contains orphaned files.
Solutions:
Check for orphaned files:
var stats = Cache.GetPersistentCacheStatistics(); if (stats.OrphanedFiles > 0) { Console.WriteLine($"Found {stats.OrphanedFiles} orphaned files"); }
Clean up expired files:
Cache.CleanupExpiredPersistentCache();
Manual cleanup (if needed):
var options = Cache.GetPersistentCacheOptions(); if (options != null && Directory.Exists(options.BaseDirectory)) { // Backup and clean the directory if necessary }
Getting Help
If you encounter issues not covered here:
- Check the CHANGELOG.md for version-specific information
- Review the comprehensive examples in this README
- Use the monitoring features to diagnose issues:
var metadata = Cache.GetAllCacheMetadata(); var stats = Cache.GetPersistentCacheStatistics();
- Create an issue on the GitHub repository with detailed reproduction steps
Performance considerations
- The CacheUtility uses locks to ensure thread safety, but is designed to minimize lock contention.
- Populate methods are only called once per cache miss, even under high concurrency.
- Refresh operations are non-blocking: Cache calls return immediately with existing data, even during background refresh.
- Background refresh uses
Task.Run()
to prevent blocking the main thread. - Multiple concurrent refresh requests for the same cache key are automatically deduplicated.
- Consider memory usage when caching large objects or collections.
- Persistent cache performance:
- Memory cache remains the primary storage for optimal performance
- File I/O operations are performed asynchronously when possible
- JSON serialization overhead is minimal for most data types
- Disk storage provides fallback without impacting memory cache speed
- Background cleanup runs periodically without blocking cache operations
When to use cache groups vs. key prefixes
- Cache groups: Use when you need to invalidate multiple related items at once.
- Key prefixes: Use within your keys when you want to organize items but may need more granular control.
API Reference
Core Cache Operations
Get Methods
// Basic get with populate method
T Get<T>(string cacheKey, string groupName, Func<T> populateMethod)
// Get with sliding expiration
T Get<T>(string cacheKey, string groupName, TimeSpan slidingExpiration, Func<T> populateMethod)
// Get with absolute expiration
T Get<T>(string cacheKey, string groupName, DateTime absoluteExpiration, Func<T> populateMethod)
// Get with auto-refresh
T Get<T>(string cacheKey, string groupName, TimeSpan slidingExpiration, Func<T> populateMethod, TimeSpan refresh)
// Get with callback
T Get<T>(string cacheKey, string groupName, TimeSpan slidingExpiration, Func<T> populateMethod, CacheEntryRemovedCallback removedCallback)
Remove Methods
// Remove single item
void Remove(string cacheKey, string groupName)
// Remove entire group
void RemoveGroup(string groupName)
// Remove all cache items
void RemoveAll()
Group Operations
// Get all items from a group
IEnumerable<T> GetAllByGroup<T>(string groupName)
// Add dependency between groups
void AddGroupDependency(string dependentGroup, string parentGroup)
Persistent Cache Operations
Configuration
// Enable with defaults
void EnablePersistentCache()
// Enable with custom options
void EnablePersistentCache(PersistentCacheOptions options)
// Disable persistent cache
void DisablePersistentCache()
// Check if enabled
bool IsPersistentCacheEnabled { get; }
// Get current options
PersistentCacheOptions GetPersistentCacheOptions()
Management
// Get comprehensive statistics
PersistentCacheStatistics GetPersistentCacheStatistics()
// Manual cleanup of expired files
void CleanupExpiredPersistentCache()
Monitoring and Metadata
Metadata Operations
// Get metadata for all cached items
IEnumerable<CacheItemMetadata> GetAllCacheMetadata()
CacheItemMetadata Properties
public class CacheItemMetadata
{
// Basic Information
public string CacheKey { get; set; }
public string GroupName { get; set; }
public string DataType { get; set; }
public long EstimatedMemorySize { get; set; }
// Refresh Information
public DateTime LastRefreshTime { get; set; }
public DateTime? LastRefreshAttempt { get; set; }
public TimeSpan RefreshInterval { get; set; }
public DateTime? NextRefreshTime { get; set; }
public bool IsRefreshing { get; set; }
public DateTime? RefreshStartTime { get; set; }
// Collection Information
public int? CollectionCount { get; set; }
// Method Information
public string PopulateMethodName { get; set; }
// Persistent Cache Information
public bool PersistentCacheEnabled { get; set; }
public bool IsPersisted { get; set; }
public string PersistentFilePath { get; set; }
public string PersistentMetaFilePath { get; set; }
public long PersistentFileSize { get; set; }
public long PersistentMetaFileSize { get; set; }
public long TotalPersistentSize { get; }
public DateTime? LastPersistedTime { get; set; }
public TimeSpan? PersistentFileAge { get; }
}
PersistentCacheStatistics Properties
public class PersistentCacheStatistics
{
// Basic Information
public bool IsEnabled { get; set; }
public string BaseDirectory { get; set; }
// File Counts
public int TotalFiles { get; set; }
public int CacheFiles { get; set; }
public int MetaFiles { get; set; }
public int OrphanedFiles { get; set; }
// Size Information
public long TotalSizeBytes { get; set; }
public string TotalSizeFormatted { get; }
public long AverageFileSize { get; }
public string AverageFileSizeFormatted { get; }
public long LargestFileSize { get; set; }
public long SmallestFileSize { get; set; }
// Time Information
public DateTime? OldestFileTime { get; set; }
public DateTime? NewestFileTime { get; set; }
public TimeSpan? DirectoryAge { get; }
public TimeSpan? TimeSinceLastActivity { get; }
}
PersistentCacheOptions Properties
public class PersistentCacheOptions
{
// Base directory for cache files (default: %LOCALAPPDATA%/CacheUtility/)
public string BaseDirectory { get; set; }
// Maximum size for individual cache files in bytes (default: 10MB)
public long MaxFileSize { get; set; }
}
Utility Methods
Cache Management
// Dispose all resources
void Dispose()
// Check if item exists in cache
bool Exists(string cacheKey, string groupName)
Performance Benchmarks
Based on internal testing with 1,000 cache operations:
Operation | Memory Cache | Persistent Cache (Enabled) | Overhead |
---|---|---|---|
Cache Hit | ~0.001ms | ~0.001ms | 0% |
Cache Miss (Population) | ~1.2ms | ~1.3ms | ~8% |
Group Removal | ~0.5ms | ~2.1ms | ~320% |
Metadata Retrieval | ~0.8ms | ~1.1ms | ~37% |
Key Findings:
- Zero overhead when persistent cache is disabled
- Minimal impact on cache hits (primary use case)
- Modest overhead on cache misses due to serialization
- Higher impact on bulk operations due to file I/O
- Memory usage remains unchanged (files are additional storage)
Recommendations:
- Enable persistent cache for data that benefits from persistence
- Use fast storage (SSD) for cache directory
- Monitor file sizes and implement cleanup strategies
- Consider disabling for high-frequency, temporary data
Memory management
The CacheUtility is built on top of .NET's MemoryCache, which has built-in memory pressure detection. However, be mindful of:
- Setting appropriate cache priorities
- Using reasonable expiration times
- Caching only necessary data
Thread safety
All operations in CacheUtility are thread-safe. The implementation uses ReaderWriterLockSlim for efficient concurrent access and CacheLock for synchronizing modifications to the cache.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- System.Runtime.Caching (>= 9.0.7)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Major Update v1.1.0: Added persistent cache storage with JSON serialization that survives application restarts. Enhanced monitoring with comprehensive metadata including next refresh times, file ages, and detailed statistics. Added disk usage analysis with file size distribution, orphaned file detection, and cache activity tracking. Improved API with better error handling and performance optimizations. Fully backward compatible with zero performance impact when persistent cache is disabled.