La soluzione più semplice è solo fare una classe wrapper che utilizza un dizionario normale e utilizza un ReaderWriterLockSlim
per controllare filo accesso sicuro.
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();
}
}
Questa classe implementa l'interfaccia completa IDictionary<Tkey,TValue>
. Il modo in cui tutto questo funziona è che tutti gli inserimenti passano attraverso TryAdd
, se sei uguale o superiore alla dimensione massima e prova a inserire un nuovo membro ottieni false
da TryAdd
e un InvalidOperationException
da metodi che non restituiscono bool
.
Il motivo per cui non ho utilizzato un ConcurrentDictionary
non è un buon modo per provare a controllare il conteggio prima di aggiungere un nuovo membro in un modo atomic, quindi è necessario bloccarlo comunque. Potresti potenzialmente utilizzare un dizionario concorrente e rimuovere tutti i miei EnterReadLock
e sostituire lo EnterWriteLock
con le normali chiamate lock
, ma dovresti eseguire test delle prestazioni per vedere quale farebbe meglio.
Se si desiderano metodi come GetOrAdd
, non sarebbe difficile da implementare.
fonte
2014-12-10 15:12:02
'Contains' e' CopyTo' iniziano con 'ExitReadLock'. Immagino che dovrebbe essere "EnterReadLock"? Cool implementazione, però, +1 – Default
Questa è sicuramente una versione molto migliore di quello che avrei inventato se avessi provato a farlo da solo. Ottima risposta – ChrisT
C'è un vantaggio nell'usare 'ReaderWriterLockSlim' sul modo" normale "di dichiarare un' oggetto readonly privato myLock = new object(); 'e quindi circondare tutte le cose critiche con' lock (myLock) {... } '? I blocchi "prova ... finalmente" dappertutto danneggiano la leggibilità (imho). – Corak