i found this interface and i want to use it. But i dont understand how to use the Create function...
namespace Microsoft.Extensions.Caching.Memory
{
public interface IMemoryCache : IDisposable
{
ICacheEntry CreateEntry(object key);
void Remove(object key);
bool TryGetValue(object key, out object value);
}
}
How to store something in CreateEntry when there is only the key not the value in the function call? How to store something in the key?
So i have this:
class RedisObjectTestCache : IMemoryCache
{
public ICacheEntry CreateEntry(object key)
{
Console.WriteLine("Created key: " + key);
return new CacheEntryTest() { };
}
public void Dispose()
{
Console.WriteLine("Dispose");
return;
}
public void Remove(object key)
{
Console.WriteLine("Removed key: " + key);
return;
}
public bool TryGetValue(object key, out object value)
{
Console.WriteLine("Requested key: " + key);
value = "";
return false;
}
}
and then i call it with the framework:
QueryCacheManager.Cache = new RedisObjectTestCache();
Can i somehow get the value?
The ICacheEntry
instance returned from the CreateEntry
method has a Value
property which you can set to the value you want to cache, along with several other properties you can use to control the caching.
There are also several extension methods for the IMemoryCache
interface which provide shorthand ways of setting an item in the cache.
ICacheEntry
(directly) at all.Instead, use only the Microsoft.Extensions.Caching.Memory.CacheExtensions
methods: Set<TItem>
and TryGetValue<TItem>
- specifically, the Set
extension method handles ICacheEntry
for you.
Note there's a slight gotcha with this approach: you have to be careful you're using consistent TItem
type-parameters and Object key
keys - e.g. if you cache a List<String>
using Set< List<String> >(...)
but later use TryGetValue< String[] >
then your code will silently fail and return null
- instead of complaining that a cached List<String>
cannot be retrieved as a String[]
. (Of course, you probably shouldn't be caching mutable types like List<T>
, instead you should cache something like ImmutableList<String>
and only retrieve items via a compatible interface like IReadOnlyList<String>
(which means you can also store String[]
and List<String>
in addition to ImmutableList<String>
).
ICacheEntry
and call .Dispose()
immediately after setting .Value
There's a huge (still undocumented) gotcha with ICacheEntry
: while the design of the API surface of the entire Microsoft.Extensions.Caching.Memory
library implies that the ICacheEntry
object represents a named/keyed slot in the IMemoryCache
that you can keep in a long-life'd field or static
field - and can manipulate any time you like - this is not the case: the ICacheEntry
returned actually represents an uncomitted cache entry - so simply setting .Value
won't do anything until you call .Dispose()
on the ICacheEntry
(or use a using(){}
block).
The CacheExtensions.Set
method does this for you: observe the using ICacheEntry entry = cache.CreateEntry(key);
line in Set
.
So, by example, if you are going to use ICacheEntry
directly, do not use it like this as it will not work:
public class ThingDoer_Bad
{
// DO NOT USE THIS CODE - IT IS AN EXAMPLE OF HOW IMEMORYCACHE IS MISUNDERSTOOD:
private readonly IMemoryCache memCache; // From DI
private const String CACHE_KEY = "TheThing123";
private ICacheEntry? entry;
public void AddThingToCacheOrOverwriteExistingThing( Thing thing )
{
this.entry = this.memCache.CreateEntry( CACHE_KEY );
this.entry.Value = thing;
this.entry.Expiration = whatever;
}
public void UseCachedThing()
{
if( this.entry != null )
{
Console.WriteLine( this.entry.Value ); // <-- This will appear to work.
}
}
}
...in the above code, the ThingDoer_Bad.AddThingToCacheOrOverwriteExistingThing
method doesn't actually do anything useful - people might think it works because if you retrieve this.entry.Value
it will return the stored Thing
object, but the Thing
object won't be in the cache - you'll just be working with an normal stored object-reference just with extra steps.
This is how you're meant to use it, by not retaining ICacheEntry
at all and disposing it immediately:
public class ThingDoer
{
private readonly IMemoryCache memCache; // From DI
private const String CACHE_KEY = "TheThing123";
public void AddThingToCacheOrOverwriteExistingThing( Thing thing )
{
using( ICacheEntry e = this.memCache.CreateEntry( CACHE_KEY ) )
{
e.Value = thing;
e.Expiration = whatever;
} // <-- The implicit .Dispose() call at the end of the `using` block is what actually triggers persistence.
}
public void UseCachedThing()
{
if( this.memCache.TryGetValue( CACHE_KEY, out Thing? maybeThing ) )
{
Console.WriteLine( cachedThing );
}
}
}
struct
to handle get/set of single itemspublic readonly struct TypedMemoryCacheEntry<T> : IEquatable< TypedMemoryCacheEntry<T> >
where T : class
{
private readonly IMemoryCache cache;
private readonly String key;
public TypedMemoryCacheEntry( IMemoryCache cache, String key )
{
this.cache = cache ?? throw new ArgumentNullException( nameof(cache) );
this.key = key ?? throw new ArgumentNullException( nameof(key) );
}
public String Key => this.key;
public void Set( T value, DateTimeOffset absoluteExpiration )
{
if( value is null ) throw new ArgumentNullException( nameof(value) );
_ = this.cache.Set<T>( key: this.key, value: value, absoluteExpiration: absoluteExpiration );
}
public Boolean TryGet( [NotNullWhen(true)] out T? value )
{
return this.cache.TryGetValue<T>( key: this.key, out value );
}
#region Tedium
public override Boolean Equals( Object? obj ) => obj is TypedMemoryCacheEntry<T> other && this.Equals( other: other );
public Boolean Equals( TypedMemoryCacheEntry<T> other ) => StringComparer.Ordinal.Equals( this.key, other.key ) && Object.ReferenceEquals( this.cache, other.cache );
public override Int32 GetHashCode() => HashCode.Combine( this.cache.GetHashCode(), this.key.GetHashCode() );
public static Boolean operator==( TypedMemoryCacheEntry<T> left, TypedMemoryCacheEntry<T> right ) => left.Equals( other: right );
public static Boolean operator!=( TypedMemoryCacheEntry<T> left, TypedMemoryCacheEntry<T> right ) => !left.Equals( other: right );
#endregion
}
...then by then storing TypedMemoryCacheEntry<T>
as a field you don't need to worry about keeping track of consistent TItem
parameters in every call-site:
public class ThingDoer
{
private readonly IMemoryCache memCache;
private readonly TypedMemoryCacheEntry<Thing> cacheEntry;
public ThingDoer( IMemoryCache memCache )
{
this.memCache = memCache ?? throw new ArgumentNullException(nameof(memCache));
thus.cacheEntry = new TypedMemoryCacheEntry<Thing>( this.memCache, key: "TheThing123" );
}
public void AddThingToCacheOrOverwriteExistingThing( Thing thing )
{
this.cacheEntry.Set( thing, expiration: etc );
}
public void UseCachedThing()
{
if( this.cacheEntry.TryGetValue( out Thing? maybeThing ) )
{
Console.WriteLine( cachedThing );
}
}
}
Much simpler, imo - I don't know why something useful and straightforward to use like this wasn't included in the Microsoft.Extensions.Caching.Abstractions
library.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With