I am after a collection with the following properties:
BlockingCollection<T>.TryAdd(T)
would be perfect, i.e. it would return false if the maximum number of elements has been reachedConcurrentDictionary
would be perfect, i.e. ability to identify elements by a key, remove any item (not just the first or last, which I think would be the limitation with BlockingCollection
)Before I attempt to roll my own, my questions are:
BlockingCollection
somehow?Finally, if I do need to try and make my own, what approach should I think about? Is it as simple as a wrapped Dictionary
with locks
?
Example use: A chat room with a defined limit on number of participants could store the connection information of participants and reject new entrants until there is room to enter when full
The simplest solution is just make a wrapper class that uses a normal dictionary and uses a ReaderWriterLockSlim
to control thread safe access.
public class SizeLimitedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly int _maxSize;
private readonly IDictionary<TKey, TValue> _dictionary;
private readonly ReaderWriterLockSlim _readerWriterLock;
public SizeLimitedDictionary(int maxSize)
{
_maxSize = maxSize;
_dictionary = new Dictionary<TKey, TValue>(_maxSize);
_readerWriterLock = new ReaderWriterLockSlim();
}
public bool TryAdd(TKey key, TValue value)
{
_readerWriterLock.EnterWriteLock();
try
{
if (_dictionary.Count >= _maxSize)
return false;
_dictionary.Add(key, value);
}
finally
{
_readerWriterLock.ExitWriteLock();
}
return true;
}
public void Add(TKey key, TValue value)
{
bool added = TryAdd(key, value);
if(!added)
throw new InvalidOperationException("Dictionary is at max size, can not add additional members.");
}
public bool TryAdd(KeyValuePair<TKey, TValue> item)
{
_readerWriterLock.EnterWriteLock();
try
{
if (_dictionary.Count >= _maxSize)
return false;
_dictionary.Add(item);
}
finally
{
_readerWriterLock.ExitWriteLock();
}
return true;
}
public void Add(KeyValuePair<TKey, TValue> item)
{
bool added = TryAdd(item);
if (!added)
throw new InvalidOperationException("Dictionary is at max size, can not add additional members.");
}
public void Clear()
{
_readerWriterLock.EnterWriteLock();
try
{
_dictionary.Clear();
}
finally
{
_readerWriterLock.ExitWriteLock();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
_readerWriterLock.EnterReadLock();
try
{
return _dictionary.Contains(item);
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_readerWriterLock.EnterReadLock();
try
{
_dictionary.CopyTo(array, arrayIndex);
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
_readerWriterLock.EnterWriteLock();
try
{
return _dictionary.Remove(item);
}
finally
{
_readerWriterLock.ExitWriteLock();
}
}
public int Count
{
get
{
_readerWriterLock.EnterReadLock();
try
{
return _dictionary.Count;
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
}
public bool IsReadOnly
{
get
{
_readerWriterLock.EnterReadLock();
try
{
return _dictionary.IsReadOnly;
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
}
public bool ContainsKey(TKey key)
{
_readerWriterLock.EnterReadLock();
try
{
return _dictionary.ContainsKey(key);
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
public bool Remove(TKey key)
{
_readerWriterLock.EnterWriteLock();
try
{
return _dictionary.Remove(key);
}
finally
{
_readerWriterLock.ExitWriteLock();
}
}
public bool TryGetValue(TKey key, out TValue value)
{
_readerWriterLock.EnterReadLock();
try
{
return _dictionary.TryGetValue(key, out value);
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
public TValue this[TKey key]
{
get
{
_readerWriterLock.EnterReadLock();
try
{
return _dictionary[key];
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
set
{
_readerWriterLock.EnterUpgradeableReadLock();
try
{
var containsKey = _dictionary.ContainsKey(key);
_readerWriterLock.EnterWriteLock();
try
{
if (containsKey)
{
_dictionary[key] = value;
}
else
{
var added = TryAdd(key, value);
if(!added)
throw new InvalidOperationException("Dictionary is at max size, can not add additional members.");
}
}
finally
{
_readerWriterLock.ExitWriteLock();
}
}
finally
{
_readerWriterLock.ExitUpgradeableReadLock();
}
}
}
public ICollection<TKey> Keys
{
get
{
_readerWriterLock.EnterReadLock();
try
{
return _dictionary.Keys;
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
}
public ICollection<TValue> Values
{
get
{
_readerWriterLock.EnterReadLock();
try
{
return _dictionary.Values;
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_dictionary).GetEnumerator();
}
}
This class implements the full IDictionary<Tkey,TValue>
interface. The way this works is all insertions pass through TryAdd
, if you are at or above the max size and try to insert a new member you get a false
from TryAdd
and a InvalidOperationException
from methods that do not return bool
.
The reason I did not use a ConcurrentDictionary
is there is no good way to try to check the count before adding a new member in an atomic way, so you would need to lock anyway. You could potentially use a concurrent dictionary and remove all of my EnterReadLock
's and replace the EnterWriteLock
's with normal lock
calls, but you would need to do performance testing to see which would do better.
If you want methods like GetOrAdd
it would not be hard to implement yourself.