To implement a generic thread-safe cache class in C#, the goal is to create a class that allows you to store, retrieve, and remove key-value pairs in a thread-safe manner. Additionally, the cache should include an expiration mechanism so that items are automatically removed after a certain period.
Here's a step-by-step guide on how to accomplish this:
1. Understanding the Requirements
- Generic Class: The cache should be able to work with any type of key (
TKey) and value (TValue). Hence, we need a generic class. - Thread-Safe: The class must handle concurrent access by multiple threads without causing data corruption or unexpected behavior.
- Expiration Mechanism: Items in the cache should have an expiration time after which they are automatically considered invalid and should not be returned from the cache.
2. Choosing the Right Data Structure
- Thread-Safety: C# provides the
ConcurrentDictionary<TKey, TValue>class in theSystem.Collections.Concurrentnamespace, which is ideal for this purpose. It is designed to handle concurrent operations and provides thread-safe methods for adding, retrieving, and removing items. - CacheItem: Since we need to store both the value and the expiration time for each entry, we should define a
CacheItemclass that contains these two pieces of information.
3. Designing the CacheItem Class
-
The
CacheItemclass will have two properties:- Value: The cached value of type
TValue. - ExpiryTime: A
DateTimeindicating when the item should expire.
- Value: The cached value of type
-
The class will also have a method
IsExpired()to check if the current time is past the expiry time.
4. Implementing the Cache Class
- Data Storage: Use a
ConcurrentDictionary<TKey, CacheItem>to store the cache items. - Add Method: The
Addmethod should accept a key, a value, and an expiration duration. It will create aCacheItemand store it in the dictionary. - Retrieve Method: The
TryGetValuemethod will retrieve the item if it exists and is not expired. If the item is expired, it should be removed from the cache. - Remove Method: The
Removemethod will allow manual removal of items from the cache. - Clear Method (Optional): A method to clear all items from the cache.
5. Handling Expiration
- Every time an item is accessed (via
TryGetValue), its expiration status is checked. If the item has expired, it is removed from the cache, and the method returnsfalse, indicating the item is no longer valid.
6. Putting It All Together: The Code
Here’s how the code would look:
using System;
using System.Collections.Concurrent;
public class ThreadSafeCache<TKey, TValue>
{
// The CacheItem class holds the value and its expiration time
private class CacheItem
{
public TValue Value { get; }
public DateTime ExpiryTime { get; }
public CacheItem(TValue value, TimeSpan duration)
{
Value = value;
ExpiryTime = DateTime.UtcNow.Add(duration);
}
public bool IsExpired() => DateTime.UtcNow > ExpiryTime;
}
// ConcurrentDictionary to hold the cache items
private readonly ConcurrentDictionary<TKey, CacheItem> _cache = new ConcurrentDictionary<TKey, CacheItem>();
// Method to add an item to the cache
public void Add(TKey key, TValue value, TimeSpan duration)
{
var cacheItem = new CacheItem(value, duration);
_cache[key] = cacheItem; // Adds or updates the value in the cache
}
// Method to retrieve an item from the cache
public bool TryGetValue(TKey key, out TValue value)
{
if (_cache.TryGetValue(key, out CacheItem cacheItem))
{
if (!cacheItem.IsExpired())
{
value = cacheItem.Value;
return true;
}
else
{
// If the item is expired, remove it from the cache
_cache.TryRemove(key, out _);
}
}
value = default;
return false;
}
// Method to remove an item from the cache
public bool Remove(TKey key)
{
return _cache.TryRemove(key, out _);
}
// Optional: Method to clear the cache
public void Clear()
{
_cache.Clear();
}
}
7. Key Points in the Implementation
- Thread-Safety: The
ConcurrentDictionaryhandles all thread-safety concerns for adding, retrieving, and removing items. There’s no need for explicit locks or synchronization primitives, which simplifies the implementation. - Expiration Check: The expiration logic is handled within the
TryGetValuemethod. If an item is expired, it’s removed from the cache immediately, ensuring that the cache only returns valid items. - Flexible Expiration: The cache allows you to specify different expiration durations for different items, offering flexibility in how long each item should remain valid.