using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Pfz.Caching;
using Pfz.Threading;
namespace Pfz.Collections
{
/// <summary>
/// Dictionary that uses a ReaderWriterLock for its accesses (so the call to each method
/// is thread-safe) and also gets "trimmed" automatically when garbage collections occur.
///
/// It is also capable of auto-collecting Values that are empty collections and does not
/// accept null values (or remove an item set to null).
/// </summary>
public class AutoTrimDictionary<TKey, TValue>:
ReaderWriterSafeDisposable,
IDictionary<TKey, TValue>,
IGarbageCollectionAware
{
private Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();
/// <summary>
/// Creates a new instance of AutoTrimDictionary.
/// </summary>
public AutoTrimDictionary()
{
GCUtils.RegisterForCollectedNotification(this);
}
/// <summary>
/// Unregisters this dictionary from GCUtils.Collected.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
GCUtils.UnregisterFromCollectedNotification(this);
_dictionary = null;
}
base.Dispose(disposing);
}
void IGarbageCollectionAware.OnCollected()
{
try
{
using(var upgradeableLock = DisposeLock.UpgradeableLock())
{
if (WasDisposed)
{
GCUtils.UnregisterFromCollectedNotification(this);
return;
}
Dictionary<TKey, TValue> newDictionary;
if (MustRemoveEmptyCollections)
{
var oldDictionary = _dictionary;
newDictionary = new Dictionary<TKey, TValue>(_dictionary.Count);
foreach(var pair in oldDictionary)
{
var key = pair.Key;
var value = pair.Value;
IEnumerable<object> enumerable = value as IEnumerable<object>;
if (enumerable != null)
using (var enumerator = enumerable.GetEnumerator())
if (!enumerator.MoveNext())
continue;
newDictionary.Add(key, value);
}
}
else
newDictionary = new Dictionary<TKey, TValue>(_dictionary);
upgradeableLock.Upgrade();
_dictionary = newDictionary;
}
}
catch
{
}
}
/// <summary>
/// Gets or sets a value indicating that values that are empty-collections must be removed
/// during a collection.
/// </summary>
public bool MustRemoveEmptyCollections { get; set; }
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public void Add(TKey key, TValue value)
{
if (value == null)
throw new ArgumentNullException("value");
using(DisposeLock.WriteLock())
{
CheckUndisposed();
_dictionary.Add(key, value);
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public bool ContainsKey(TKey key)
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
return _dictionary.ContainsKey(key);
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public ICollection<TKey> Keys
{
get
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
return _dictionary.Keys.ToList();
}
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public bool Remove(TKey key)
{
using(DisposeLock.WriteLock())
{
CheckUndisposed();
return _dictionary.Remove(key);
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public bool TryGetValue(TKey key, out TValue value)
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
return _dictionary.TryGetValue(key, out value);
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public ICollection<TValue> Values
{
get
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
return _dictionary.Values.ToList();
}
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public TValue this[TKey key]
{
get
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
return _dictionary[key];
}
}
set
{
if (value == null)
throw new ArgumentNullException("value");
using(DisposeLock.WriteLock())
{
CheckUndisposed();
_dictionary[key] = value;
}
}
}
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
if (item.Value == null)
throw new ArgumentException("item.Value can't be null.");
using(DisposeLock.WriteLock())
{
CheckUndisposed();
_dictionary.Add(item.Key, item.Value);
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public void Clear()
{
using(DisposeLock.WriteLock())
{
CheckUndisposed();
_dictionary.Clear();
}
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
ICollection<KeyValuePair<TKey, TValue>> dictionary = _dictionary;
return dictionary.Contains(item);
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
ICollection<KeyValuePair<TKey, TValue>> dictionary = _dictionary;
dictionary.CopyTo(array, arrayIndex);
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public int Count
{
get
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
return _dictionary.Count;
}
}
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
{
get
{
return false;
}
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
using(DisposeLock.WriteLock())
{
CheckUndisposed();
ICollection<KeyValuePair<TKey, TValue>> dictionary = _dictionary;
return dictionary.Remove(item);
}
}
/// <summary>
/// Redirects the call to the real dictionary, in a thread-safe manner.
/// </summary>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
return _dictionary.ToList().GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}