As other answers point out, current implementation of Microsoft.Extensions.Caching.Memory.MemoryCache does not expose any members allowing to retrieve all cache keys, although there is a way around the problem if we use reflection.
This answer is partially based upon the one by MarkM, adds some speed to the solution by reducing reflection usage to a minimum, adds support for Microsoft.Extensions.Caching.Memory version 7 and packs everything into a single extension class.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Microsoft.Extensions.Caching.Memory;
public static class MemoryCacheExtensions
{
#region Microsoft.Extensions.Caching.Memory_6_OR_OLDER
private static readonly Lazy<Func<MemoryCache, object>> GetEntries6 =
new Lazy<Func<MemoryCache, object>>(() => (Func<MemoryCache, object>)Delegate.CreateDelegate(
typeof(Func<MemoryCache, object>),
typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance).GetGetMethod(true),
throwOnBindFailure: true));
#endregion
#region Microsoft.Extensions.Caching.Memory_7_OR_NEWER
private static readonly Lazy<Func<MemoryCache, object>> GetCoherentState =
new Lazy<Func<MemoryCache, object>>(() =>
CreateGetter<MemoryCache, object>(typeof(MemoryCache)
.GetField("_coherentState", BindingFlags.NonPublic | BindingFlags.Instance)));
private static readonly Lazy<Func<object, IDictionary>> GetEntries7 =
new Lazy<Func<object, IDictionary>>(() =>
CreateGetter<object, IDictionary>(typeof(MemoryCache)
.GetNestedType("CoherentState", BindingFlags.NonPublic)
.GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance)));
private static Func<TParam, TReturn> CreateGetter<TParam, TReturn>(FieldInfo field)
{
var methodName = $"{field.ReflectedType.FullName}.get_{field.Name}";
var method = new DynamicMethod(methodName, typeof(TReturn), new[] { typeof(TParam) }, typeof(TParam), true);
var ilGen = method.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldfld, field);
ilGen.Emit(OpCodes.Ret);
return (Func<TParam, TReturn>)method.CreateDelegate(typeof(Func<TParam, TReturn>));
}
#endregion
private static readonly Func<MemoryCache, IDictionary> GetEntries =
Assembly.GetAssembly(typeof(MemoryCache)).GetName().Version.Major < 7
? (Func<MemoryCache, IDictionary>)(cache => (IDictionary)GetEntries6.Value(cache))
: cache => GetEntries7.Value(GetCoherentState.Value(cache));
public static ICollection GetKeys(this IMemoryCache memoryCache) =>
GetEntries((MemoryCache)memoryCache).Keys;
public static IEnumerable<T> GetKeys<T>(this IMemoryCache memoryCache) =>
memoryCache.GetKeys().OfType<T>();
}
Usage:
var cache = new MemoryCache(new MemoryCacheOptions());
cache.GetOrCreate(1, ce => "one");
cache.GetOrCreate("two", ce => "two");
foreach (var key in cache.GetKeys())
Console.WriteLine($"Key: '{key}', Key type: '{key.GetType()}'");
foreach (var key in cache.GetKeys<string>())
Console.WriteLine($"Key: '{key}', Key type: '{key.GetType()}'");
Output:
Key: '1', Key type: 'System.Int32'
Key: 'two', Key type: 'System.String'
Key: 'two', Key type: 'System.String'
Notes:
- Reflection usage is reduced to a single call that builds the
GetEntriesdelegate. When we’re working with the retrievedMemoryCachekeys, reflection is not used. In contrast to a raw reflection approach, this decreases execution time and saves machine resources during traversal of long collections ofMemoryCachekeys. - In the solution we are casting
MemoryCache‘s internal dictionary toIDictionaryinstead of the nativeConcurrentDictionary<object, CacheEntry>becauseCacheEntrytype is internal. - The code was verified using a matrix of
consoleandwebapiapps based on .NET Framework 4.8, .NET 6 and 7 withMicrosoft.Extensions.Caching.Memoryversions 6 and 7. LangVersion is 7.3.