using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Pfz.Threading;
using System.Runtime.InteropServices;
namespace Pfz.Caching
{
/// <summary>
/// This is a dictionary that allow values to be collected.
/// </summary>
[Serializable]
public sealed class WeakDictionary<TKey, TValue>:
ReaderWriterSafeDisposable,
IDictionary<TKey, TValue>,
ISerializable,
IGarbageCollectionAware
where
TValue: class
{
#region Static Area
private static Func<TValue> _valueConstructor = _GetDefaultConstructorDelegate();
private static Func<TValue> _GetDefaultConstructorDelegate()
{
if (typeof(TValue).GetConstructor(Type.EmptyTypes) == null)
return null;
return ReflectionHelper.GetDefaultConstructorDelegate<TValue>(typeof(TValue));
}
#endregion
#region Private dictionary of KeepAliveGCHandle
private Dictionary<TKey, GCHandle> _dictionary = new Dictionary<TKey, GCHandle>();
#endregion
#region Constructor
/// <summary>
/// Creates the dictionary.
/// </summary>
public WeakDictionary()
{
GCUtils.RegisterForCollectedNotification(this);
}
#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.UnregisterFromCollectedNotification(this);
var dictionary = _dictionary;
if (dictionary != null)
{
_dictionary = null;
foreach(GCHandle handle in dictionary.Values)
handle.Free();
}
base.Dispose(disposing);
}
#endregion
#region _Collected
private int _collectionCount;
void IGarbageCollectionAware.OnCollected()
{
try
{
using(DisposeLock.WriteLock())
{
if (WasDisposed)
{
GCUtils.UnregisterFromCollectedNotification(this);
return;
}
var oldDictionary = _dictionary;
var newDictionary = new Dictionary<TKey, GCHandle>(oldDictionary.Count);
foreach(var pair in oldDictionary)
{
var handle = pair.Value;
if (handle.Target != null)
newDictionary.Add(pair.Key, pair.Value);
else
handle.Free();
}
_dictionary = newDictionary;
_collectionCount++;
}
}
catch
{
}
}
#endregion
#region Properties
#region Count
/// <summary>
/// Gets the number of items in this dictionary.
/// </summary>
public int Count
{
get
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
return _dictionary.Count;
}
}
}
#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");
using(DisposeLock.ReadLock())
{
CheckUndisposed();
GCHandle handle;
if (_dictionary.TryGetValue(key, out handle))
return (TValue)handle.Target;
}
return default(TValue);
}
set
{
if (key == null)
throw new ArgumentNullException("key");
using(DisposeLock.WriteLock())
{
CheckUndisposed();
if (value == null)
_Remove(key);
else
{
GCHandle handle;
var dictionary = _dictionary;
if (dictionary.TryGetValue(key, out handle))
handle.Target = value;
else
_Add(key, value, dictionary);
}
}
}
}
#endregion
#region Keys
/// <summary>
/// Gets the Keys that exist in this dictionary.
/// </summary>
public ICollection<TKey> Keys
{
get
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
return _dictionary.Keys.ToArray();
}
}
}
#endregion
#region Values
/// <summary>
/// Gets a copy of the values that exist in this dictionary.
/// </summary>
public ICollection<TValue> Values
{
get
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
var dictionary = _dictionary;
List<TValue> result = new List<TValue>();
foreach(GCHandle handle in dictionary.Values)
{
TValue item = (TValue)handle.Target;
if (item != null)
result.Add(item);
}
return result;
}
}
}
#endregion
#endregion
#region Methods
#region _CreateValue
private static TValue _CreateValue(TKey key, Func<TKey, TValue> createValue)
{
if (createValue != null)
return createValue(key);
if (_valueConstructor == null)
throw new InvalidOperationException("Type " + typeof(TValue).FullName + " does not has a public default constructor.");
return _valueConstructor();
}
#endregion
#region Clear
/// <summary>
/// Clears all items in this dictionary.
/// </summary>
public void Clear()
{
using(DisposeLock.WriteLock())
{
CheckUndisposed();
var dictionary = _dictionary;
try
{
}
finally
{
foreach(GCHandle handle in dictionary.Values)
handle.Free();
dictionary.Clear();
}
}
}
#endregion
#region GetOrCreateValue
/// <summary>
/// Gets the value for the given key or, if it does not exist, creates it, adds it and
/// returns it.
/// If a createValue is not given, the default constructor (if any) is used.
/// </summary>
public TValue GetOrCreateValue(TKey key, Func<TKey, TValue> createValue=null)
{
if (key == null)
throw new ArgumentNullException("key");
bool isHandleGot;
GCHandle handle;
int initialCollectionCount = -1;
using(DisposeLock.ReadLock())
{
CheckUndisposed();
var dictionary = _dictionary;
isHandleGot = dictionary.TryGetValue(key, out handle);
if (isHandleGot)
{
object result = handle.Target;
if (result != null)
return (TValue)result;
initialCollectionCount = _collectionCount;
}
}
using(var upgradeableLock = DisposeLock.UpgradeableLock())
{
CheckUndisposed();
TValue typedResult;
if (isHandleGot && initialCollectionCount == _collectionCount)
{
object result = handle.Target;
if (result != null)
return (TValue)result;
typedResult = _CreateValue(key, createValue);
upgradeableLock.Upgrade();
handle.Target = typedResult;
return typedResult;
}
var dictionary = _dictionary;
if (dictionary.TryGetValue(key, out handle))
{
object result = handle.Target;
if (result != null)
return (TValue)result;
typedResult = _CreateValue(key, createValue);
upgradeableLock.Upgrade();
handle.Target = typedResult;
return typedResult;
}
typedResult = _CreateValue(key, createValue);
upgradeableLock.Upgrade();
_Add(key, typedResult, dictionary);
return typedResult;
}
}
#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");
using(DisposeLock.WriteLock())
{
CheckUndisposed();
var dictionary = _dictionary;
GCHandle handle;
if (dictionary.TryGetValue(key, out handle))
{
if (handle.Target != null)
throw new ArgumentException("An element with the same key \"" + key + "\" already exists.");
handle.Target = value;
}
else
_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");
using(DisposeLock.WriteLock())
{
CheckUndisposed();
return _Remove(key);
}
}
#endregion
#region ContainsKey
/// <summary>
/// Gets a value indicating if an item with the specified key exists.
/// </summary>
public bool ContainsKey(TKey key)
{
return _dictionary.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()
{
using(DisposeLock.ReadLock())
{
CheckUndisposed();
var dictionary = _dictionary;
var result = new List<KeyValuePair<TKey, TValue>>(dictionary.Count);
foreach(var pair in dictionary)
{
TValue target = (TValue)pair.Value.Target;
if (target != null)
result.Add(new KeyValuePair<TKey, TValue>(pair.Key, target));
}
return result;
}
}
#endregion
#region _Add
[SuppressMessage("Microsoft.Usage", "CA2219:DoNotRaiseExceptionsInExceptionClauses")]
private static void _Add(TKey key, TValue value, Dictionary<TKey, GCHandle> dictionary)
{
try
{
}
finally
{
GCHandle handle = GCHandle.Alloc(value, GCHandleType.Weak);
try
{
dictionary.Add(key, handle);
}
catch
{
handle.Free();
throw;
}
}
}
#endregion
#region _Remove
private bool _Remove(TKey key)
{
var dictionary = _dictionary;
GCHandle handle;
if (!dictionary.TryGetValue(key, out handle))
return false;
bool result;
try
{
}
finally
{
handle.Free();
result = dictionary.Remove(key);
}
return result;
}
#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;
GCHandle handle;
if (!_dictionary.TryGetValue(item.Key, out handle))
return false;
return object.Equals(handle.Target, 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;
using(var upgradeableLock = DisposeLock.UpgradeableLock())
{
CheckUndisposed();
GCHandle handle;
var dictionary = _dictionary;
if (!dictionary.TryGetValue(item.Key, out handle))
return false;
if (!object.Equals(handle.Target, item.Value))
return false;
upgradeableLock.Upgrade();
return _Remove(item.Key);
}
}
#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>
internal WeakDictionary(SerializationInfo info, StreamingContext context):
this()
{
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
}
#endregion
#endregion
}
}