Last active
December 18, 2015 03:58
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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