Добрый день!
Понадобилось тут сделать совсем примитивный кэш, чтобы лишний раз в базу не лазить. При этом данные в базе статичные, и вопрос не столько в обновлении данных, сколько в том, когда их выбросить, чтобы просто не занимать память, ну и простота использования конечно важна. Сначала хотел использовать MemoryCache, но мне показалось он слишком замороченный, да ещё и без строгой типизации.
За примером реализации прошу под кат.
Реализация
Общая идея такая: кэш должен быть удобным и прозрачным в использовании. Я вдохновился интерфейсом класса ThreadLocal. Поэтому в конструкторе требуется фабричный метод для получения исходных значений, а потом они уже кэшируются. Опять же, в моём случае, я посчитал вполне логичным держать данные на слабых ссылках (WeakReference), таким образом, пока данные нужны, кто-то их использует, они будут доступны. Дальше, по мере выделения новой памяти, данные из кэша будут вытесняться.
Есть всего два метода: индексатор, который вернёт данные либо из источника, либо из кэша.
А также метод CleanCache, чтобы очищать словарь от мёртвых ссылок и таким образом устранить утечку памяти. В реальных условиях далеко не всегда это необходимо. Например, в моих условиях новые записи в кэше будут появляться всего несколько раз в день, так что даже за годы работы ссылки не сожрут сколько-то значительный объём памяти.
Цель публикации
В основном, мне интересно услышать мнение о моей реализации, как бы вы сделали, есть ли ошибки. Ну и если кому-то это поможет — берите, используйте :)
Исходник
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();
}
}
}
}