Introduction
Cache refers to memory that allows high-speed data exchange; it communicates with the CPU before memory, making it very fast. Since reading data from memory by the CPU is several orders of magnitude faster than reading from disk, and caching reduces the pressure on database access, almost every project uses caching. Commonly used options include MemoryCache and Redis. Today, I'll introduce you to the usage of MemoryCache!
Types
Memory cache expiration times come in 4 types:
- Never expires
- Absolute expiration time
- Expiration time relative to now
- Sliding expiration time
Of course, you can also derive combinations like sliding window + absolute expiration, etc., from these three expiration types.
Official Website
You can also check the official documentation to learn more about MemoryCache. I won't elaborate much here.
- MemoryCache address: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.caching.memorycache?redirectedfrom=MSDN&view=dotnet-plat-ext-6.0
Usage
Let's get back to the topic and introduce how to set expiration times!
Never Expires
This means that once the program is deployed, the cache remains valid as long as we don't clear it.
/// <summary>
/// Never expires
/// </summary>
static void NeverExpire()
{
_cache.Set("NeverExpire", "1");
}
Absolute Expiration Time
Uses an absolute time point, which can be understood as a "deadline."
static void AbsoluteExpiration()
{
DateTime time = new DateTime(2022, 04, 01, 23, 59, 59);
_cache.Set("AbsoluteExpiration", "20220401235959", time);
}
Expiration Time Relative to Now
Expiration time relative to the present, for example, the cache is valid for one minute after being set. This can be compared to common SMS login scenarios: the backend randomly generates a verification code, stores it in Redis, and sets an expiration time for that key. Then verification occurs: the user sends their phone number and verification code to the server, and the server retrieves the corresponding verification code from Redis to verify. If correct, the verification code is deleted to prevent multiple verifications.
static void ExpirationTimeRelativeToThePresent()
{
_cache.Set("AbsoluteExpiration", "123456", new TimeSpan(0, 0, 60));
}
Sliding Expiration Time
The cache expires if it hasn't been used within the set time. When used, the cache's expiration time is refreshed.
static void SlidingExpirationTime()
{
_cache.Set("SlidingExpirationTime", "3", new MemoryCacheEntryOptions()
{
SlidingExpiration = new TimeSpan(0, 0, 2),
AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(1000)
});
}
Let's look at the official definition as shown in the image:

Now, let me explain the second parameter, MemoryCacheEntryOptions: Setting the absolute expiration date of the cache entry to 1000 seconds after the current cache setting (perhaps it should be minutes? Just kidding – in real scenarios, it's usually 5 minutes, 10 minutes, etc., depending on business design). Think of popular games like League of Legends: Wild Rift. If we don't log in for a day, the cached token expires, and we need to log in again to get a new token. If we play every day, the sliding expiration is triggered, so we don't need to log in on every app startup. But after some time, we still need to re-login – that's the absolute expiration within sliding expiration.
Getting a Cache Value
ConcurrentDictionary<object, CacheEntry> _entries: A thread-safe dictionary type. The essence of caching is this dictionary – all cache entries are stored in it. You retrieve the CacheEntry entity (which contains the key and value we set in code) by using the dictionary's key (which is the same as the key in CacheEntry).
static void GetCache()
{
// Method 1
_cache.Get("NeverExpire").ToString();
// Method 2
string value = "";
if (!_cache.TryGetValue("NeverExpire", out value))
{
throw new Exception("The cache does not exist or has expired");
}
}
Clearing a Cache Value
static void GetCache()
{
string value = "";
if (_cache.TryGetValue("NeverExpire", out value))
{
_cache.Remove("NeverExpire");
}
}
You may notice that when removing, we don't actually need the value, yet we still use a temporary variable. Isn't that a bit tedious?
C# has already addressed this issue. Starting from C# 7.0, discards are supported. Discards not only improve writing and semantics but also reduce memory allocation.
Let's simplify the code above:
static void GetCache()
{
if (_cache.TryGetValue("NeverExpire", out _))
{
_cache.Remove("NeverExpire");
}
}
Complete Code
class Program
{
public static IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
static void Main(string[] args)
{
_cache.Get("NeverExpire").ToString();
string value = "";
if (!_cache.TryGetValue("NeverExpire", out value))
{
throw new Exception("The cache does not exist or has expired");
}
if (_cache.TryGetValue("NeverExpire", out value))
{
_cache.Remove("NeverExpire");
}
if (_cache.TryGetValue("NeverExpire", out _))
{
_cache.Remove("NeverExpire");
}
}
/// <summary>
/// Never expires
/// </summary>
static void NeverExpire()
{
_cache.Set("NeverExpire", "1");
}
/// <summary>
/// Absolute expiration time
/// </summary>
static void AbsoluteExpiration()
{
DateTime time = new DateTime(2022, 04, 01, 23, 59, 59);
_cache.Set("AbsoluteExpiration", "20220401235959", time);
}
/// <summary>
/// Expiration time relative to now
/// </summary>
///
static void ExpirationTimeRelativeToThePresent()
{
_cache.Set("AbsoluteExpiration", "123456", new TimeSpan(0, 0, 60));
}
/// <summary>
/// Sliding expiration time
/// </summary>
static void SlidingExpirationTime()
{
_cache.Set("key3", "3", new MemoryCacheEntryOptions()
{
SlidingExpiration = new TimeSpan(0, 0, 2),
AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(1000)
});
}
}
Finally, if you like my article, please give it a like and follow. I hope the .NET ecosystem continues to thrive!