Introduction
Memory caching is a technique that reduces the requirement to acquire data from slower storage systems by storing frequently used data in memory. Expiration is a typical caching option that ensures the cache stays fresh and doesn't use too much memory by automatically removing cached items after a set amount of time. A variation on this is sliding expiry, in which an item's expiration time is increased each time it is accessed.
Implementation Overview
This guide explains how to use the ConcurrentDictionary class for thread-safe access to create a memory cache with sliding expiration in C#.
Components
- MemoryCache<TKey, TValue>: The primary cache class, MemoryCache<TKey, TValue>, is in charge of keeping track of and organizing cached data.
- CacheItem<T>: A helper class that stores the value and expiration time of an item in the cache and represents it.
- Timer: A background timer that's used to find and delete expired cache items on a regular basis.
Initialization
Make an instance of MemoryCache<TKey, TValue> with a default expiration time in order to use the memory cache.
var cache = new MemoryCache<string, string>(TimeSpan.FromSeconds(30));
Adding Items to the Cache
To add or change objects in the cache, use the Set method. You can also give each item a sliding expiration time.
const string infoKey = "Info";
const string jaiminKey = "Jaimin";
cache.Set(infoKey, DateTime.Now);
cache.Set(jaiminKey, DateTime.Now.AddMinutes(3));
Retrieving Items from the Cache
To get objects out of the cache, use the TryGet function. An item's sliding expiration window will be extended if it is accessed during that period.
DateTime value;
if (cache.TryGet(jaiminKey, out value))
{
Console.WriteLine($"Value for {nameof(jaiminKey)}: {value}");
}
else
{
Console.WriteLine($"{nameof(jaiminKey)} not found in cache");
}
Cache Expiration
The background timer automatically deletes expired items from the cache.
Example
var cache = new CacheHelper<string, DateTime>(TimeSpan.FromSeconds(5));
const string infoKey = "Info";
const string jaiminKey = "Jaimin";
cache.Set(infoKey, DateTime.Now);
cache.Set(jaiminKey, DateTime.Now.AddMinutes(3));
Console.WriteLine();
Console.WriteLine();
DateTime value;
if (cache.TryGet(infoKey, out value))
{
Console.WriteLine($"Value for {nameof(infoKey)}: {value}");
}
else
{
Console.WriteLine($"{nameof(infoKey)} not found in cache");
}
if (cache.TryGet(jaiminKey, out value))
{
Console.WriteLine($"Value for {nameof(jaiminKey)}: {value}");
}
else
{
Console.WriteLine($"{nameof(jaiminKey)} not found in cache");
}
Console.WriteLine();
// Wait for some time and get the data from the cache
Thread.Sleep(4000);
if (cache.TryGet(infoKey, out value))
{
Console.WriteLine($"Value for {nameof(infoKey)}: {value}");
}
else
{
Console.WriteLine($"{nameof(infoKey)} not found in cache");
}
if (cache.TryGet(jaiminKey, out value))
{
Console.WriteLine($"Value for {nameof(jaiminKey)}: {value}");
}
else
{
Console.WriteLine($"{nameof(jaiminKey)} not found in cache");
}
Console.WriteLine();
// Wait for cache expiration
Thread.Sleep(8000);
if (cache.TryGet(jaiminKey, out value))
{
Console.WriteLine($"Value for {nameof(jaiminKey)} after expiration: {value}");
}
else
{
Console.WriteLine($"{nameof(jaiminKey)} not found in cache after expiration");
}
Conclusion
You may effectively manage cached data and make sure it stays relevant and fresh for as long as it is viewed inside the designated expiration window by designing a memory cache with sliding expiration.
We learned the new technique and evolved together.
Happy coding! 😊