namespace Caliburn.Micro.Core {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
///
/// A dictionary in which the values are weak references.
///
/// The type of keys in the dictionary.
/// The type of values in the dictionary.
internal class WeakValueDictionary : IDictionary
where TValue : class {
private readonly Dictionary inner;
private readonly WeakReference gcSentinel = new WeakReference(new object());
#region Cleanup handling
private bool IsCleanupNeeded() {
if (gcSentinel.Target == null) {
gcSentinel.Target = new object();
return true;
}
return false;
}
private void CleanAbandonedItems() {
var keysToRemove = inner.Where(pair => !pair.Value.IsAlive)
.Select(pair => pair.Key)
.ToList();
keysToRemove.Apply(key => inner.Remove(key));
}
private void CleanIfNeeded() {
if (IsCleanupNeeded()) {
CleanAbandonedItems();
}
}
#endregion
#region Constructors
///
/// Initializes a new instance of the class that is empty, has the default initial capacity, and uses the default equality comparer for the key type.
///
public WeakValueDictionary() {
inner = new Dictionary();
}
///
/// Initializes a new instance of the class that contains elements copied from the specified and uses the default equality comparer for the key type.
///
/// The whose elements are copied to the new .
public WeakValueDictionary(IDictionary dictionary) {
inner = new Dictionary();
dictionary.Apply(item => inner.Add(item.Key, new WeakReference(item.Value)));
}
///
/// Initializes a new instance of the class that contains elements copied from the specified and uses the specified .
///
/// The whose elements are copied to the new .
/// The implementation to use when comparing keys, or null to use the default for the type of the key.
public WeakValueDictionary(IDictionary dictionary, IEqualityComparer comparer) {
inner = new Dictionary(comparer);
dictionary.Apply(item => inner.Add(item.Key, new WeakReference(item.Value)));
}
///
/// Initializes a new instance of the class that is empty, has the default initial capacity, and uses the specified .
///
/// The implementation to use when comparing keys, or null to use the default for the type of the key.
public WeakValueDictionary(IEqualityComparer comparer) {
inner = new Dictionary(comparer);
}
///
/// Initializes a new instance of the class that is empty, has the specified initial capacity, and uses the default equality comparer for the key type.
///
/// The initial number of elements that the can contain.
public WeakValueDictionary(int capacity) {
inner = new Dictionary(capacity);
}
///
/// Initializes a new instance of the class that is empty, has the specified initial capacity, and uses the specified .
///
/// The initial number of elements that the can contain.
/// The implementation to use when comparing keys, or null to use the default for the type of the key.
public WeakValueDictionary(int capacity, IEqualityComparer comparer) {
inner = new Dictionary(capacity, comparer);
}
#endregion
///
/// Returns an enumerator that iterates through the .
///
/// The enumerator.
public IEnumerator> GetEnumerator() {
CleanIfNeeded();
var enumerable = inner.Select(pair => new KeyValuePair(pair.Key, (TValue) pair.Value.Target))
.Where(pair => pair.Value != null);
return enumerable.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
void ICollection>.Add(KeyValuePair item) {
Add(item.Key, item.Value);
}
///
/// Removes all keys and values from the .
///
public void Clear() {
inner.Clear();
}
bool ICollection>.Contains(KeyValuePair item) {
TValue value;
if (!TryGetValue(item.Key, out value))
return false;
return value == item.Value;
}
void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) {
if (array == null)
throw new ArgumentNullException("array");
if (arrayIndex < 0 || arrayIndex >= array.Length)
throw new ArgumentOutOfRangeException("arrayIndex");
if ((arrayIndex + Count) > array.Length)
throw new ArgumentException(
"The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.");
this.ToArray().CopyTo(array, arrayIndex);
}
bool ICollection>.Remove(KeyValuePair item) {
TValue value;
if (!TryGetValue(item.Key, out value))
return false;
if (value != item.Value)
return false;
return inner.Remove(item.Key);
}
///
/// Gets the number of key/value pairs contained in the .
///
///
/// Since the items in the dictionary are held by weak reference, the count value
/// cannot be relied upon to guarantee the number of objects that would be discovered via
/// enumeration. Treat the Count as an estimate only.
///
public int Count {
get {
CleanIfNeeded();
return inner.Count;
}
}
bool ICollection>.IsReadOnly {
get { return false; }
}
///
/// Adds the specified key and value to the dictionary.
///
/// The key of the element to add.
/// The value of the element to add. The value can be null for reference types.
public void Add(TKey key, TValue value) {
CleanIfNeeded();
inner.Add(key, new WeakReference(value));
}
///
/// Determines whether the contains the specified key.
///
/// The key to locate in the .
///
public bool ContainsKey(TKey key) {
TValue dummy;
return TryGetValue(key, out dummy);
}
///
/// Removes the value with the specified key from the .
///
/// The key of the element to remove.
/// true if the element is successfully found and removed; otherwise, false. This method returns false if key is not found in the .
public bool Remove(TKey key) {
CleanIfNeeded();
return inner.Remove(key);
}
///
/// Gets the value associated with the specified key.
///
/// The key of the value to get.
///
/// When this method returns, contains the value associated with the specified key,
/// if the key is found; otherwise, the default value for the type of the value parameter.
/// This parameter is passed uninitialized.
/// true if the contains an element with the specified key; otherwise, false.
public bool TryGetValue(TKey key, out TValue value) {
CleanIfNeeded();
WeakReference wr;
if (!inner.TryGetValue(key, out wr)) {
value = null;
return false;
}
var result = (TValue) wr.Target;
if (result == null) {
inner.Remove(key);
value = null;
return false;
}
value = result;
return true;
}
///
/// Gets or sets the value associated with the specified key.
///
/// The key of the value to get or set.
///
/// The value associated with the specified key. If the specified key is not found, a get operation throws a ,
/// and a set operation creates a new element with the specified key.
///
public TValue this[TKey key] {
get {
TValue result;
if (!TryGetValue(key, out result))
throw new KeyNotFoundException();
return result;
}
set {
CleanIfNeeded();
inner[key] = new WeakReference(value);
}
}
///
/// Gets a collection containing the keys in the .
///
public ICollection Keys {
get { return inner.Keys; }
}
///
/// Gets a collection containing the values in the .
///
public ICollection Values {
get { return new ValueCollection(this); }
}
#region Inner Types
private sealed class ValueCollection : ICollection {
private readonly WeakValueDictionary inner;
public ValueCollection(WeakValueDictionary dictionary) {
inner = dictionary;
}
public IEnumerator GetEnumerator() {
return inner.Select(pair => pair.Value).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
void ICollection.Add(TValue item) {
throw new NotSupportedException();
}
void ICollection.Clear() {
throw new NotSupportedException();
}
bool ICollection.Contains(TValue item) {
return inner.Any(pair => pair.Value == item);
}
public void CopyTo(TValue[] array, int arrayIndex) {
if (array == null)
throw new ArgumentNullException("array");
if (arrayIndex < 0 || arrayIndex >= array.Length)
throw new ArgumentOutOfRangeException("arrayIndex");
if ((arrayIndex + Count) > array.Length)
throw new ArgumentException(
"The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.");
this.ToArray().CopyTo(array, arrayIndex);
}
bool ICollection.Remove(TValue item) {
throw new NotSupportedException();
}
public int Count {
get { return inner.Count; }
}
bool ICollection.IsReadOnly {
get { return true; }
}
}
#endregion
}
}