using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Pfz.Extensions.MonitorLockExtensions;
using Pfz.Threading;
namespace Pfz.Caching
{
/// <summary>
/// This is a dictionary that allow values to be collected.
/// </summary>
[Serializable]
public class WeakDictionary<TKey, TValue>:
ThreadSafeDisposable,
IDictionary<TKey, TValue>,
ISerializable
where
TValue: class
{
#region Private dictionary of EquatableWeakReferences
private volatile Dictionary<TKey, KeepAliveGCHandle> fDictionary = new Dictionary<TKey,KeepAliveGCHandle>();
#endregion
#region Constructor
/// <summary>
/// Creates the dictionary.
/// </summary>
public WeakDictionary()
{
GCUtils.Collected += p_Collected;
}
#endregion
#region Dispose
/// <summary>
/// Frees all handles used to know if an item was collected or not.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
GCUtils.Collected -= p_Collected;
var dictionary = fDictionary;
if (dictionary != null)
{
fDictionary = null;
foreach(KeepAliveGCHandle wr in dictionary.Values)
wr.Free();
}
base.Dispose(disposing);
}
#endregion
#region p_Collected
private void p_Collected()
{
try
{
DisposeLock.TryLockWithTimeout
(
delegate
{
if (WasDisposed)
{
GCUtils.Collected -= p_Collected;
return;
}
var oldDictionary = fDictionary;
var newDictionary = new Dictionary<TKey, KeepAliveGCHandle>(oldDictionary.Count);
foreach(var pair in oldDictionary)
{
var wr = pair.Value;
if (wr.IsAlive)
newDictionary.Add(pair.Key, pair.Value);
else
wr.Free();
}
fDictionary = newDictionary;
}
);
}
catch
{
}
}
#endregion
#region Properties
#region Count
/// <summary>
/// Gets the number of items in this dictionary.
/// </summary>
public int Count
{
get
{
int result = 0;
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
result = fDictionary.Count;
}
);
return result;
}
}
#endregion
#region this[]
/// <summary>
/// Gets or sets a value for the specified key.
/// Returns null if the item does not exist. The indexer, when
/// used as an IDictionary throws an exception when the item does
/// not exist.
/// </summary>
public TValue this[TKey key]
{
get
{
if (key == null)
throw new ArgumentNullException("key");
TValue result = default(TValue);
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
KeepAliveGCHandle wr;
if (!fDictionary.TryGetValue(key, out wr))
return;
result = (TValue)wr.Target;
}
);
return result;
}
set
{
if (key == null)
throw new ArgumentNullException("key");
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
if (value == null)
p_Remove(key);
else
{
KeepAliveGCHandle wr;
var dictionary = fDictionary;
if (dictionary.TryGetValue(key, out wr))
wr.Target = value;
else
p_Add(key, value, dictionary);
}
}
);
}
}
#endregion
#region Keys
/// <summary>
/// Gets the Keys that exist in this dictionary.
/// </summary>
public ICollection<TKey> Keys
{
get
{
ICollection<TKey> result = null;
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
result = fDictionary.Keys.ToArray();
}
);
return result;
}
}
#endregion
#region Values
/// <summary>
/// Gets the values that exist in this dictionary.
/// </summary>
public ICollection<TValue> Values
{
get
{
ICollection<TValue> baseResult = null;
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
var dictionary = fDictionary;
List<TValue> result = new List<TValue>();
foreach(KeepAliveGCHandle wr in dictionary.Values)
{
TValue item = (TValue)wr.TargetAllowingExpiration;
if (item != null)
result.Add(item);
}
baseResult = result;
}
);
return baseResult;
}
}
#endregion
#endregion
#region Methods
#region Clear
/// <summary>
/// Clears all items in this dictionary.
/// </summary>
public void Clear()
{
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
var dictionary = fDictionary;
foreach(KeepAliveGCHandle wr in dictionary.Values)
wr.Free();
dictionary.Clear();
}
);
}
#endregion
#region Add
/// <summary>
/// Adds an item to this dictionary. Throws an exception if an item
/// with the same key already exists.
/// </summary>
public void Add(TKey key, TValue value)
{
if (key == null)
throw new ArgumentNullException("key");
if (value == null)
throw new ArgumentNullException("value");
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
var dictionary = fDictionary;
KeepAliveGCHandle wr;
if (dictionary.TryGetValue(key, out wr))
{
if (wr.IsAlive)
throw new ArgumentException("An element with the same key \"" + key + "\" already exists.");
wr.Target = value;
}
else
p_Add(key, value, dictionary);
}
);
}
#endregion
#region Remove
/// <summary>
/// Removes an item with the given key from the dictionary.
/// </summary>
public bool Remove(TKey key)
{
if (key == null)
throw new ArgumentNullException("key");
bool result = false;
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
result = p_Remove(key);
}
);
return result;
}
#endregion
#region ContainsKey
/// <summary>
/// Gets a value indicating if an item with the specified key exists.
/// </summary>
public bool ContainsKey(TKey key)
{
return fDictionary.ContainsKey(key);
}
#endregion
#region GetEnumerator
/// <summary>
/// Gets an enumerator with all key/value pairs that exist in
/// this dictionary.
/// </summary>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return ToList().GetEnumerator();
}
#endregion
#region ToList
/// <summary>
/// Gets a list with all non-collected key/value pairs.
/// </summary>
public List<KeyValuePair<TKey, TValue>> ToList()
{
List<KeyValuePair<TKey, TValue>> result = new List<KeyValuePair<TKey, TValue>>();
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
var dictionary = fDictionary;
foreach(var pair in dictionary)
{
TValue target = (TValue)pair.Value.TargetAllowingExpiration;
if (target != null)
result.Add(new KeyValuePair<TKey, TValue>(pair.Key, target));
}
}
);
return result;
}
#endregion
#region p_Add
private static void p_Add(TKey key, TValue value, Dictionary<TKey, KeepAliveGCHandle> dictionary)
{
try
{
}
finally
{
KeepAliveGCHandle wr = new KeepAliveGCHandle(value);
try
{
dictionary.Add(key, wr);
}
catch
{
wr.Free();
throw;
}
}
}
#endregion
#region p_Remove
private bool p_Remove(TKey key)
{
var dictionary = fDictionary;
KeepAliveGCHandle wr;
if (!dictionary.TryGetValue(key, out wr))
return false;
wr.Free();
return dictionary.Remove(key);
}
#endregion
#endregion
#region Interfaces
#region IDictionary<TKey,TValue> Members
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value)
{
value = this[key];
return value != null;
}
TValue IDictionary<TKey, TValue>.this[TKey key]
{
get
{
TValue result = this[key];
if (result == null)
throw new KeyNotFoundException("The given key \"" + key + "\" was not found in the dictionary.");
return result;
}
set
{
this[key] = value;
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
if (item.Value == null)
return false;
KeepAliveGCHandle wr;
if (!fDictionary.TryGetValue(item.Key, out wr))
return false;
return object.Equals(wr.TargetAllowingExpiration, item.Value);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
ToList().CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
{
get
{
return false;
}
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
if (item.Value == null)
return false;
bool result = false;
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
KeepAliveGCHandle wr;
var dictionary = fDictionary;
if (!dictionary.TryGetValue(item.Key, out wr))
return;
if (!object.Equals(wr.TargetAllowingExpiration, item.Value))
return;
result = p_Remove(item.Key);
}
);
return result;
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region ISerializable Members
/// <summary>
/// Creates the dictionary from serialization info.
/// Actually, it does not load anything, as if everything was
/// collected.
/// </summary>
protected WeakDictionary(SerializationInfo info, StreamingContext context):
this()
{
}
/// <summary>
/// It is here to be inherited. Actually, this does not
/// add anything, as if everything was collected.
/// </summary>
protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
DisposeLock.LockWithTimeout
(
delegate
{
CheckUndisposed();
GetObjectData(info, context);
}
);
}
#endregion
#endregion
}
}