Introduction
In .NET 9 Preview 1, a new Keys property has been added to MemoryCache, allowing retrieval of cache keys. Additionally, a new overload of GetOrCreate (and its async counterpart) now accepts a MemoryCacheEntryOptions parameter.
Sample Usage
Keys Sample
New API:
public class MemoryCache
{
public System.Collections.Generic.IEnumerable<object> Keys { get; }
}
Example without dependency injection:
Console.WriteLine("MemoryCache keys non-di sample");
var cache = new MemoryCache(new MemoryCacheOptions());
cache.Set("user:1:roles", "user,manager");
cache.Set("user:1:permission", "read,write");
cache.Set("user:2:roles", "user");
cache.Set("user:2:permission", "read");
foreach (var key in cache.Keys)
{
Console.WriteLine(key.ToString());
if (key is string keyStr && keyStr.StartsWith("user:2:"))
{
cache.Remove(key);
}
}
Console.WriteLine("some keys had been removed");
foreach (var key in ((MemoryCache)cache).Keys)
{
Console.WriteLine(key.ToString());
}
This example simulates clearing cache entries for user 2 and prints all cache keys.
Output:

Example with dependency injcetion:
Console.WriteLine("MemoryCache keys dependency injection sample");
using var services = new ServiceCollection()
.AddMemoryCache()
.BuildServiceProvider();
var cache = services.GetRequiredService<IMemoryCache>();
cache.Set("user:1:roles", "user,manager");
cache.Set("user:1:permission", "read,write");
cache.Set("user:2:roles", "user");
cache.Set("user:2:permission", "read");
foreach (var key in ((MemoryCache)cache).Keys)
{
Console.WriteLine(key.ToString());
if (key is string keyStr && keyStr.StartsWith("user:2:"))
{
cache.Remove(key);
}
}
Console.WriteLine("some keys had been removed");
foreach (var key in ((MemoryCache)cache).Keys)
{
Console.WriteLine(key.ToString());
}
Output:

Note that Keys is defined on MemoryCache, not on IMemoryCache, so casting is required when using DI.
GetOrCreate/GetOrCreateAsync Overload
New API:
namespace Microsoft.Extensions.Caching.Memory
{
public static class CacheExtensions
{
public static TItem? GetOrCreate<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, TItem> factory, MemoryCacheEntryOptions? createOptions);
public static Task<TItem?> GetOrCreateAsync<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, Task<TItem>> factory, MemoryCacheEntryOptions? createOptions);
}
}
These overloads allow passing MemoryCacheEntryOptions to set expiration policies, simplifying the factory method.
Example usage:
Console.WriteLine("MemoryCache GetOrCreate with options sample");
using var services = new ServiceCollection()
.AddMemoryCache()
.BuildServiceProvider();
var cache = services.GetRequiredService<IMemoryCache>();
var key = "user:2:roles";
FetchValue();
Thread.Sleep(1000);
FetchValue();
Thread.Sleep(3000);
FetchValue();
void FetchValue()
{
cache.GetOrCreate("no-expiration", _ =>
{
Console.WriteLine("fetch value from source for no-expiration key");
return "user";
});
cache.GetOrCreate(key, _ =>
{
Console.WriteLine("fetch value from source");
return "user";
},
new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3) });
}
Here, two cache entries are used for comparison: one without expiration and one with a 3-second absolute expiration. The output shows that the expiring entry is fetched twice (initial and after expiration), while the non-expiring entry is fetched only once.
Output:
