Good day!
It was necessary to make a very primitive cache here, so as not to climb into the database once again. At the same time, the data in the database is static, and the question is not so much in updating the data, but in when to drop them, just to not take up memory, and of course ease of use is important. At first I wanted to use MemoryCache, but it seemed to me too confused, and even without strict typing.
For an example of implementation, please under the cat.
')
Implementation
The general idea is this: the cache should be comfortable and transparent to use. I was inspired by the interface class ThreadLocal. Therefore, the constructor requires a factory method to get the original values, and then they are cached. Again, in my case, I considered it quite logical to keep the data on weak links (WeakReference), so, as long as the data is needed, someone uses it, it will be available. Further, as the allocation of new memory, the data from the cache will be pushed out.
There are only two methods: the indexer, which will return data either from the source or from the cache.
As well as the CleanCache method, to clear the dictionary of dead references and thus eliminate memory leaks. In reality, it is not always necessary. For example, in my conditions, new entries in the cache will appear only a few times a day, so even over the years the links will not devour a significant amount of memory.
Purpose of publication
Basically, I'm interested in hearing about my implementation, how would you do if there are any mistakes. Well, if it helps someone, take it, use it :)
Source code
public class WeakCache<TKey,TValue> where TValue : class { const int cacheCleanInterval = 60; private readonly Func<TKey, TValue> getter; private readonly Dictionary<TKey, WeakReference> data = new Dictionary<TKey, WeakReference>(); private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(); private DateTime lastCacheClean = DateTime.MinValue; public WeakCache(Func<TKey,TValue> getter) { this.getter = getter; } public TValue this[TKey key] { get { CleanCache(); try { rwLock.EnterUpgradeableReadLock(); WeakReference wr; TValue val; if (data.TryGetValue(key, out wr)) { val = (TValue)wr.Target; if (val != null) return val; } try { rwLock.EnterWriteLock(); if (data.TryGetValue(key, out wr)) { val = (TValue)wr.Target; if (val != null) return val; } data[key] = new WeakReference(val = getter(key)); return val; } finally { rwLock.ExitWriteLock(); } } finally { rwLock.ExitUpgradeableReadLock(); } } } void CleanCache() { if ((DateTime.Now - lastCacheClean).TotalSeconds > cacheCleanInterval) { try { rwLock.EnterWriteLock(); if ((DateTime.Now - lastCacheClean).TotalSeconds > cacheCleanInterval) { lastCacheClean = DateTime.Now; var refs = data.ToArray(); foreach (var weakReference in refs) { if (!weakReference.Value.IsAlive) data.Remove(weakReference.Key); } } } finally { rwLock.ExitWriteLock(); } } } }