patterncsharpMinorCanonical
In-memory cache implementation revisited
Viewed 0 times
revisitedimplementationcachememory
Problem
As a follow-up on my earlier question, here is the new implementation. The major change is now that the base class just deals in
Unfortunately requires some casting in the derived classes but I don't think there is a way around it in C#.
Looking for general review, best practices, design improvements, hidden bugs etc.
Cache base class
It's now abstract and requires the implementation of the
```
public abstract class Cache
{
protected interface ICacheValue
{
TValue Value { get; set; }
}
protected readonly Dictionary _ValueCache = new Dictionary();
protected object SyncRoot = new object();
protected abstract ICacheValue CreateCacheValue(TValue value);
protected abstract void UpdateElementAccess(TKey key, ICacheValue cacheValue);
protected abstract void CacheValueInvalidated(ICacheValue cacheValue);
public virtual int Count
{
get { return _ValueCache.Count; }
}
public bool TryGetValue(TKey key, out TValue value)
{
ICacheValue v;
value = default(TValue);
lock (SyncRoot)
{
v = GetCacheValueUnlocked(key);
if (v != null)
{
value = v.Value;
UpdateElementAccess(key, v);
return true;
}
}
return false;
}
protected virtual ICacheValue GetCacheValueUnlocked(TKey key)
{
ICacheValue v;
return _ValueCache.TryGetValue(key, out v) ? v : null;
}
public void SetValue(TKey key, TValue value)
{
lock (SyncRoot)
{
SetValueUnl
ICacheValue values which are implemented by the derived classes and generated through implementation of a factory method.Unfortunately requires some casting in the derived classes but I don't think there is a way around it in C#.
Looking for general review, best practices, design improvements, hidden bugs etc.
Cache base class
It's now abstract and requires the implementation of the
CreateCacheValue factory method. It only deals with the basic read/write access of items. All public methods are protected by a lock which call internal unlocked methods which are all virtual so they can get overwritten by the derived cache implementations.```
public abstract class Cache
{
protected interface ICacheValue
{
TValue Value { get; set; }
}
protected readonly Dictionary _ValueCache = new Dictionary();
protected object SyncRoot = new object();
protected abstract ICacheValue CreateCacheValue(TValue value);
protected abstract void UpdateElementAccess(TKey key, ICacheValue cacheValue);
protected abstract void CacheValueInvalidated(ICacheValue cacheValue);
public virtual int Count
{
get { return _ValueCache.Count; }
}
public bool TryGetValue(TKey key, out TValue value)
{
ICacheValue v;
value = default(TValue);
lock (SyncRoot)
{
v = GetCacheValueUnlocked(key);
if (v != null)
{
value = v.Value;
UpdateElementAccess(key, v);
return true;
}
}
return false;
}
protected virtual ICacheValue GetCacheValueUnlocked(TKey key)
{
ICacheValue v;
return _ValueCache.TryGetValue(key, out v) ? v : null;
}
public void SetValue(TKey key, TValue value)
{
lock (SyncRoot)
{
SetValueUnl
Solution
Unfortunately requires some casting in the derived classes but I don't think there is a way around it in C#.
There is a way around that.
Define a 3rd template parameter to specify the type of cache value; use a
Use the specific TCacheValue type instead of ICacheValue throughout the implementation of the Cache class:
Now you can specify a subclass as follows, without using down-casts in the implementation, specifying Cache in the methods' signatures instead of ICache:
There is a way around that.
Define a 3rd template parameter to specify the type of cache value; use a
where constraint on the 3rd parameter; and change the interface from protected to public (so that you can use it in the definition of the public Cache class):public abstract class Cache
where TCacheValue : class, Cache.ICacheValue
{
public interface ICacheValue
{
TValue Value { get; set; }
}Use the specific TCacheValue type instead of ICacheValue throughout the implementation of the Cache class:
protected readonly Dictionary _ValueCache = new Dictionary();
protected object SyncRoot = new object();
protected abstract TCacheValue CreateCacheValue(TValue value);
protected abstract void UpdateElementAccess(TKey key, TCacheValue cacheValue);
protected abstract void CacheValueInvalidated(TCacheValue cacheValue);
public virtual int Count
{
get { return _ValueCache.Count; }
}
public bool TryGetValue(TKey key, out TValue value)
{
TCacheValue v;
value = default(TValue);
lock (SyncRoot)
{
v = GetCacheValueUnlocked(key);
if (v != null)
{
value = v.Value;
UpdateElementAccess(key, v);
return true;
}
}
return false;
}
protected virtual TCacheValue GetCacheValueUnlocked(TKey key)
{
TCacheValue v;
return _ValueCache.TryGetValue(key, out v) ? v : null;
}
public void SetValue(TKey key, TValue value)
{
lock (SyncRoot)
{
SetValueUnlocked(key, value);
}
}
protected virtual TCacheValue SetValueUnlocked(TKey key, TValue value)
{
TCacheValue cacheValue = GetCacheValueUnlocked(key);
if (cacheValue == null)
{
cacheValue = CreateCacheValue(value);
_ValueCache[key] = cacheValue;
}
else
{
cacheValue.Value = value;
}
UpdateElementAccess(key, cacheValue);
return cacheValue;
}
public void Invalidate(TKey key)
{
lock (SyncRoot)
{
InvalidateUnlocked(key);
}
}
protected virtual void InvalidateUnlocked(TKey key)
{
var value = GetCacheValueUnlocked(key);
if (value != null)
{
_ValueCache.Remove(key);
CacheValueInvalidated(value);
}
}
public virtual void Flush()
{
lock (SyncRoot)
{
FlushUnlocked();
}
}
protected virtual void FlushUnlocked()
{
_ValueCache.Clear();
}
public List GetKeys()
{
lock (SyncRoot)
{
return new List(_ValueCache.Keys);
}
}
}Now you can specify a subclass as follows, without using down-casts in the implementation, specifying Cache in the methods' signatures instead of ICache:
public class SizeLimitedCache : Cache.CacheValue>
{
public sealed class CacheValue : ICacheValue
{
public CacheValue(TValue value)
{
Value = value;
}
public LinkedListNode> IndexRef { get; set; }
public TValue Value { get; set; }
}
private readonly LinkedList> _IndexList = new LinkedList>();
public int MaxSize { get; set; }
public SizeLimitedCache(int maxSize)
{
MaxSize = maxSize;
}
protected override void UpdateElementAccess(TKey key, CacheValue cacheValue)
{
var value = cacheValue;
// put element at front of the index list
// remove first if already present in list, create new otherwise
var idxRef = value.IndexRef;
if (idxRef != null)
{
_IndexList.Remove(idxRef);
}
else
{
idxRef = new LinkedListNode>(new KeyValuePair(key, value));
value.IndexRef = idxRef;
}
_IndexList.AddFirst(idxRef);
// remove all entries from end of list until max size is satisfied
while (_IndexList.Count > MaxSize)
{
InvalidateUnlocked(_IndexList.Last.Value.Key);
}
}
protected sealed override CacheValue CreateCacheValue(TValue value)
{
return new CacheValue(value);
}
protected override void CacheValueInvalidated(CacheValue cacheValue)
{
_IndexList.Remove(cacheValue.IndexRef);
}
protected override void FlushUnlocked()
{
base.FlushUnlocked();
_IndexList.Clear();
}
}Code Snippets
public abstract class Cache<TKey, TValue, TCacheValue>
where TCacheValue : class, Cache<TKey, TValue, TCacheValue>.ICacheValue
{
public interface ICacheValue
{
TValue Value { get; set; }
}protected readonly Dictionary<TKey, TCacheValue> _ValueCache = new Dictionary<TKey, TCacheValue>();
protected object SyncRoot = new object();
protected abstract TCacheValue CreateCacheValue(TValue value);
protected abstract void UpdateElementAccess(TKey key, TCacheValue cacheValue);
protected abstract void CacheValueInvalidated(TCacheValue cacheValue);
public virtual int Count
{
get { return _ValueCache.Count; }
}
public bool TryGetValue(TKey key, out TValue value)
{
TCacheValue v;
value = default(TValue);
lock (SyncRoot)
{
v = GetCacheValueUnlocked(key);
if (v != null)
{
value = v.Value;
UpdateElementAccess(key, v);
return true;
}
}
return false;
}
protected virtual TCacheValue GetCacheValueUnlocked(TKey key)
{
TCacheValue v;
return _ValueCache.TryGetValue(key, out v) ? v : null;
}
public void SetValue(TKey key, TValue value)
{
lock (SyncRoot)
{
SetValueUnlocked(key, value);
}
}
protected virtual TCacheValue SetValueUnlocked(TKey key, TValue value)
{
TCacheValue cacheValue = GetCacheValueUnlocked(key);
if (cacheValue == null)
{
cacheValue = CreateCacheValue(value);
_ValueCache[key] = cacheValue;
}
else
{
cacheValue.Value = value;
}
UpdateElementAccess(key, cacheValue);
return cacheValue;
}
public void Invalidate(TKey key)
{
lock (SyncRoot)
{
InvalidateUnlocked(key);
}
}
protected virtual void InvalidateUnlocked(TKey key)
{
var value = GetCacheValueUnlocked(key);
if (value != null)
{
_ValueCache.Remove(key);
CacheValueInvalidated(value);
}
}
public virtual void Flush()
{
lock (SyncRoot)
{
FlushUnlocked();
}
}
protected virtual void FlushUnlocked()
{
_ValueCache.Clear();
}
public List<TKey> GetKeys()
{
lock (SyncRoot)
{
return new List<TKey>(_ValueCache.Keys);
}
}
}public class SizeLimitedCache<TKey, TValue> : Cache<TKey, TValue, SizeLimitedCache<TKey, TValue>.CacheValue>
{
public sealed class CacheValue : ICacheValue
{
public CacheValue(TValue value)
{
Value = value;
}
public LinkedListNode<KeyValuePair<TKey, CacheValue>> IndexRef { get; set; }
public TValue Value { get; set; }
}
private readonly LinkedList<KeyValuePair<TKey, CacheValue>> _IndexList = new LinkedList<KeyValuePair<TKey, CacheValue>>();
public int MaxSize { get; set; }
public SizeLimitedCache(int maxSize)
{
MaxSize = maxSize;
}
protected override void UpdateElementAccess(TKey key, CacheValue cacheValue)
{
var value = cacheValue;
// put element at front of the index list
// remove first if already present in list, create new otherwise
var idxRef = value.IndexRef;
if (idxRef != null)
{
_IndexList.Remove(idxRef);
}
else
{
idxRef = new LinkedListNode<KeyValuePair<TKey, CacheValue>>(new KeyValuePair<TKey, CacheValue>(key, value));
value.IndexRef = idxRef;
}
_IndexList.AddFirst(idxRef);
// remove all entries from end of list until max size is satisfied
while (_IndexList.Count > MaxSize)
{
InvalidateUnlocked(_IndexList.Last.Value.Key);
}
}
protected sealed override CacheValue CreateCacheValue(TValue value)
{
return new CacheValue(value);
}
protected override void CacheValueInvalidated(CacheValue cacheValue)
{
_IndexList.Remove(cacheValue.IndexRef);
}
protected override void FlushUnlocked()
{
base.FlushUnlocked();
_IndexList.Clear();
}
}Context
StackExchange Code Review Q#42269, answer score: 5
Revisions (0)
No revisions yet.