Skip to content

Instantly share code, notes, and snippets.

@DarinMacRae
Last active December 18, 2015 03:58
Show Gist options
  • Save DarinMacRae/5721703 to your computer and use it in GitHub Desktop.
Save DarinMacRae/5721703 to your computer and use it in GitHub Desktop.
Here is a class I wrote several years back. We needed a thread-safe dictionary and it was before .NET 4.5 shipped with its "concurrent" collections. It is mostly obsolete code now but it illustrates the kind of code I am capable of writing. It's super fast, has good error checking and handling. It supports testability and some customization.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Caching
{
public class NimbleCache<K, V> : ICache<K, V>
{
public NimbleCache(string name)
: this(name, null, false)
{
}
public NimbleCache(string name, bool isReadOnly)
: this(name, null, isReadOnly)
{
}
public NimbleCache(string name, IEqualityComparer<K> comparer)
: this(name, comparer, false)
{
}
public NimbleCache(string name, IEqualityComparer<K> comparer, bool isReadOnly)
{
if (name == null)
throw new ArgumentNullException("name");
if (name.Length == 0)
throw new ArgumentException("name");
_name = name;
_inner = new Dictionary<K, V>(comparer);
_isReadOnly = isReadOnly;
}
public NimbleCache(string name, IDictionary<K, V> dictionary, IEqualityComparer<K> comparer, bool isReadOnly)
: this(name, dictionary, comparer, c_DefaultLockAcquireTimeout, isReadOnly)
{
}
public NimbleCache(string name, IDictionary<K, V> dictionary, IEqualityComparer<K> comparer, int lockAcquireTimeout, bool isReadOnly)
{
if (name == null)
throw new ArgumentNullException("name");
if (name.Length == 0)
throw new ArgumentException("name");
if (dictionary == null)
throw new ArgumentNullException("dictionary");
_name = name;
_inner = new Dictionary<K, V>(dictionary, comparer);
_lockAcquireTimeout = lockAcquireTimeout;
_isReadOnly = isReadOnly;
}
//~Cache()
//{
// Dispose(false);
//}
public void Dispose()
{
Dispose(true);
//GC.SuppressFinalize(this);
}
public string Name
{
get
{
checkDisposed();
return _name;
}
}
public bool IsReadOnly
{
get
{
checkDisposed();
return _isReadOnly;
}
}
public event EventHandler<CacheEventArgs> Changed;
// returns the value of any item that already exists by the given key
// or default(T) if no such item exists.
public V Add(K key, V item, AddOptionsIfExists options)
{
checkDisposed();
if (_isReadOnly)
throw new CacheIsReadonlyException();
bool added = false;
V existingValue = default(V);
if (!_rwl.TryEnterWriteLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
if (_inner.TryGetValue(key, out existingValue))
{
if (options == AddOptionsIfExists.ThrowException)
throw new CacheItemAlreadyExistsException();
else if (options == AddOptionsIfExists.Replace)
_inner[key] = item;
//else CacheAddOptions.DoNothingIfExists
}
else
{
_inner.Add(key, item);
added = true;
}
}
finally
{
_rwl.ExitWriteLock();
}
if (added)
onChanged(new CacheEventArgs(_name, 1, 0));
return existingValue;
}
public IEnumerable<V> Remove(IEnumerable<K> keys, RemoveOptionsIfNotExists options)
{
if (keys == null)
throw new ArgumentNullException("keys");
checkDisposed();
if (_isReadOnly)
throw new CacheIsReadonlyException();
if (keys.Count() == 0)
return new V[0];
List<V> existingValues = new List<V>();
if (!_rwl.TryEnterWriteLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
foreach (K key in keys)
{
V existingValue = default(V);
if (_inner.TryGetValue(key, out existingValue))
{
_inner.Remove(key);
}
else
{
if (options == RemoveOptionsIfNotExists.ThrowException)
throw new CacheItemDoesNotExistException(Resources.Messages.Caching_ApplicationException_KeyDoesNotExist);
}
existingValues.Add(existingValue);
}
}
finally
{
_rwl.ExitWriteLock();
}
onChanged(new CacheEventArgs(_name, 0, existingValues.Count));
return existingValues;
}
// returns the value of any item that was removed
// or default(T) if not such item exists.
public V Remove(K key, RemoveOptionsIfNotExists options)
{
checkDisposed();
if (_isReadOnly)
throw new CacheIsReadonlyException();
V existingValue = default(V);
if (!_rwl.TryEnterWriteLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
if (_inner.TryGetValue(key, out existingValue))
{
_inner.Remove(key);
}
else
{
if (options == RemoveOptionsIfNotExists.ThrowException)
throw new CacheItemDoesNotExistException(Resources.Messages.Caching_ApplicationException_KeyDoesNotExist);
}
}
finally
{
_rwl.ExitWriteLock();
}
onChanged(new CacheEventArgs(_name, 1, 0));
return existingValue;
}
public bool TryGetValue(K key, out V item)
{
checkDisposed();
if (!_rwl.TryEnterReadLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
return _inner.TryGetValue(key, out item);
}
finally
{
_rwl.ExitReadLock();
}
}
public bool[] TryGetValues(IEnumerable<K> keys, out IEnumerable<V> items, TryGetOptionsIfNotExists options)
{
checkDisposed();
if (keys == null)
throw new ArgumentNullException("keys");
int keyCount = keys.Count();
if (keyCount == 0)
{
items = new V[0];
return new bool[0];
}
if (!_rwl.TryEnterReadLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
List<V> itemsList = new List<V>(keyCount);
bool[] exists = new bool[keyCount];
for (int i = 0; i < keyCount; i++)
{
V item;
exists[i] = _inner.TryGetValue(keys.ElementAt(i), out item);
if (!exists[i])
{
if (options == TryGetOptionsIfNotExists.ThrowException)
throw new CacheItemDoesNotExistException(Resources.Messages.Caching_ApplicationException_KeyDoesNotExist);
else if (options == TryGetOptionsIfNotExists.IncludeDefaultValue)
itemsList.Add(default(V));
// else CacheTryGetOptions.DoNothingIfNotExists
}
else
{
itemsList.Add(item);
}
}
items = itemsList.AsEnumerable();
return exists;
}
finally
{
_rwl.ExitReadLock();
}
}
public bool ContainsKey(K key)
{
checkDisposed();
if (!_rwl.TryEnterReadLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
return _inner.ContainsKey(key);
}
finally
{
_rwl.ExitReadLock();
}
}
public IEnumerable<K> GetKeys()
{
checkDisposed();
if (!_rwl.TryEnterReadLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
return (new List<K>(_inner.Keys)).AsReadOnly();
}
finally
{
_rwl.ExitReadLock();
}
}
public IEnumerable<V> GetValues()
{
checkDisposed();
if (!_rwl.TryEnterReadLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
return (new List<V>(_inner.Values)).AsReadOnly();
}
finally
{
_rwl.ExitReadLock();
}
}
public IEnumerable<KeyValuePair<K, V>> GetItems()
{
checkDisposed();
if (!_rwl.TryEnterReadLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
return (new List<KeyValuePair<K, V>>(_inner)).AsReadOnly();
}
finally
{
_rwl.ExitReadLock();
}
}
public int Count
{
get
{
checkDisposed();
if (!_rwl.TryEnterReadLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
return _inner.Count;
}
finally
{
_rwl.ExitReadLock();
}
}
}
public void Clear()
{
checkDisposed();
if (_isReadOnly)
throw new CacheIsReadonlyException();
if (!_rwl.TryEnterWriteLock(_lockAcquireTimeout))
throw new CacheLockAcquisitionException();
try
{
_inner.Clear();
}
finally
{
_rwl.ExitWriteLock();
}
}
// //////////////////////////////////////////////////////////////////////////
protected virtual void Dispose(bool disposing)
{
if (0 == Interlocked.CompareExchange(ref _disposed, 1, 0))
{
if (disposing)
{
if (_rwl != null)
_rwl.Dispose();
if (_inner != null)
_inner.Clear();
}
}
}
private void onChanged(CacheEventArgs args)
{
EventHandler<CacheEventArgs> handler = Changed;
if (handler != null)
handler(this, args);
}
private void checkDisposed()
{
if (1 == _disposed)
throw new ObjectDisposedException(this.GetType().ToString(), "This object has been disposed.");
}
private const int c_DefaultLockAcquireTimeout = 1000;
private readonly ReaderWriterLockSlim _rwl = new ReaderWriterLockSlim();
private readonly Dictionary<K, V> _inner = new Dictionary<K, V>();
private readonly string _name;
private readonly int _lockAcquireTimeout = c_DefaultLockAcquireTimeout;
private bool _isReadOnly;
private int _disposed;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment