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 } }