WeakValueDictionary.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. namespace Caliburn.Micro.Core {
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. /// <summary>
  7. /// A dictionary in which the values are weak references.
  8. /// </summary>
  9. /// <typeparam name="TKey">The type of keys in the dictionary.</typeparam>
  10. /// <typeparam name="TValue">The type of values in the dictionary.</typeparam>
  11. internal class WeakValueDictionary<TKey, TValue> : IDictionary<TKey, TValue>
  12. where TValue : class {
  13. private readonly Dictionary<TKey, WeakReference> inner;
  14. private readonly WeakReference gcSentinel = new WeakReference(new object());
  15. #region Cleanup handling
  16. private bool IsCleanupNeeded() {
  17. if (gcSentinel.Target == null) {
  18. gcSentinel.Target = new object();
  19. return true;
  20. }
  21. return false;
  22. }
  23. private void CleanAbandonedItems() {
  24. var keysToRemove = inner.Where(pair => !pair.Value.IsAlive)
  25. .Select(pair => pair.Key)
  26. .ToList();
  27. keysToRemove.Apply(key => inner.Remove(key));
  28. }
  29. private void CleanIfNeeded() {
  30. if (IsCleanupNeeded()) {
  31. CleanAbandonedItems();
  32. }
  33. }
  34. #endregion
  35. #region Constructors
  36. /// <summary>
  37. /// Initializes a new instance of the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> class that is empty, has the default initial capacity, and uses the default equality comparer for the key type.
  38. /// </summary>
  39. public WeakValueDictionary() {
  40. inner = new Dictionary<TKey, WeakReference>();
  41. }
  42. /// <summary>
  43. /// Initializes a new instance of the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> class that contains elements copied from the specified <see cref="IDictionary&lt;TKey, TValue&gt;"/> and uses the default equality comparer for the key type.
  44. /// </summary>
  45. /// <param name="dictionary">The <see cref="IDictionary&lt;TKey, TValue&gt;"/> whose elements are copied to the new <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.</param>
  46. public WeakValueDictionary(IDictionary<TKey, TValue> dictionary) {
  47. inner = new Dictionary<TKey, WeakReference>();
  48. dictionary.Apply(item => inner.Add(item.Key, new WeakReference(item.Value)));
  49. }
  50. /// <summary>
  51. /// Initializes a new instance of the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> class that contains elements copied from the specified <see cref="IDictionary&lt;TKey, TValue&gt;"/> and uses the specified <see cref="IEqualityComparer&lt;T&gt;"/>.
  52. /// </summary>
  53. /// <param name="dictionary">The <see cref="IDictionary&lt;TKey, TValue&gt;"/> whose elements are copied to the new <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.</param>
  54. /// <param name="comparer">The <see cref="IEqualityComparer&lt;T&gt;"/> implementation to use when comparing keys, or null to use the default <see cref="EqualityComparer&lt;T&gt;"/> for the type of the key.</param>
  55. public WeakValueDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
  56. inner = new Dictionary<TKey, WeakReference>(comparer);
  57. dictionary.Apply(item => inner.Add(item.Key, new WeakReference(item.Value)));
  58. }
  59. /// <summary>
  60. /// Initializes a new instance of the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> class that is empty, has the default initial capacity, and uses the specified <see cref="IEqualityComparer&lt;T&gt;"/>.
  61. /// </summary>
  62. /// <param name="comparer">The <see cref="IEqualityComparer&lt;T&gt;"/> implementation to use when comparing keys, or null to use the default <see cref="EqualityComparer&lt;T&gt;"/> for the type of the key.</param>
  63. public WeakValueDictionary(IEqualityComparer<TKey> comparer) {
  64. inner = new Dictionary<TKey, WeakReference>(comparer);
  65. }
  66. /// <summary>
  67. /// Initializes a new instance of the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> class that is empty, has the specified initial capacity, and uses the default equality comparer for the key type.
  68. /// </summary>
  69. /// <param name="capacity">The initial number of elements that the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> can contain.</param>
  70. public WeakValueDictionary(int capacity) {
  71. inner = new Dictionary<TKey, WeakReference>(capacity);
  72. }
  73. /// <summary>
  74. /// Initializes a new instance of the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> class that is empty, has the specified initial capacity, and uses the specified <see cref="IEqualityComparer&lt;T&gt;"/>.
  75. /// </summary>
  76. /// <param name="capacity">The initial number of elements that the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> can contain.</param>
  77. /// <param name="comparer">The <see cref="IEqualityComparer&lt;T&gt;"/> implementation to use when comparing keys, or null to use the default <see cref="EqualityComparer&lt;T&gt;"/> for the type of the key.</param>
  78. public WeakValueDictionary(int capacity, IEqualityComparer<TKey> comparer) {
  79. inner = new Dictionary<TKey, WeakReference>(capacity, comparer);
  80. }
  81. #endregion
  82. /// <summary>
  83. /// Returns an enumerator that iterates through the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.
  84. /// </summary>
  85. /// <returns>The enumerator.</returns>
  86. public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
  87. CleanIfNeeded();
  88. var enumerable = inner.Select(pair => new KeyValuePair<TKey, TValue>(pair.Key, (TValue) pair.Value.Target))
  89. .Where(pair => pair.Value != null);
  90. return enumerable.GetEnumerator();
  91. }
  92. IEnumerator IEnumerable.GetEnumerator() {
  93. return GetEnumerator();
  94. }
  95. void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
  96. Add(item.Key, item.Value);
  97. }
  98. /// <summary>
  99. /// Removes all keys and values from the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.
  100. /// </summary>
  101. public void Clear() {
  102. inner.Clear();
  103. }
  104. bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) {
  105. TValue value;
  106. if (!TryGetValue(item.Key, out value))
  107. return false;
  108. return value == item.Value;
  109. }
  110. void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
  111. if (array == null)
  112. throw new ArgumentNullException("array");
  113. if (arrayIndex < 0 || arrayIndex >= array.Length)
  114. throw new ArgumentOutOfRangeException("arrayIndex");
  115. if ((arrayIndex + Count) > array.Length)
  116. throw new ArgumentException(
  117. "The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.");
  118. this.ToArray().CopyTo(array, arrayIndex);
  119. }
  120. bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) {
  121. TValue value;
  122. if (!TryGetValue(item.Key, out value))
  123. return false;
  124. if (value != item.Value)
  125. return false;
  126. return inner.Remove(item.Key);
  127. }
  128. /// <summary>
  129. /// Gets the number of key/value pairs contained in the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.
  130. /// </summary>
  131. /// <remarks>
  132. /// Since the items in the dictionary are held by weak reference, the count value
  133. /// cannot be relied upon to guarantee the number of objects that would be discovered via
  134. /// enumeration. Treat the Count as an estimate only.
  135. /// </remarks>
  136. public int Count {
  137. get {
  138. CleanIfNeeded();
  139. return inner.Count;
  140. }
  141. }
  142. bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
  143. get { return false; }
  144. }
  145. /// <summary>
  146. /// Adds the specified key and value to the dictionary.
  147. /// </summary>
  148. /// <param name="key">The key of the element to add.</param>
  149. /// <param name="value">The value of the element to add. The value can be null for reference types.</param>
  150. public void Add(TKey key, TValue value) {
  151. CleanIfNeeded();
  152. inner.Add(key, new WeakReference(value));
  153. }
  154. /// <summary>
  155. /// Determines whether the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> contains the specified key.
  156. /// </summary>
  157. /// <param name="key">The key to locate in the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.</param>
  158. /// <returns></returns>
  159. public bool ContainsKey(TKey key) {
  160. TValue dummy;
  161. return TryGetValue(key, out dummy);
  162. }
  163. /// <summary>
  164. /// Removes the value with the specified key from the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.
  165. /// </summary>
  166. /// <param name="key">The key of the element to remove.</param>
  167. /// <returns>true if the element is successfully found and removed; otherwise, false. This method returns false if key is not found in the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.</returns>
  168. public bool Remove(TKey key) {
  169. CleanIfNeeded();
  170. return inner.Remove(key);
  171. }
  172. /// <summary>
  173. /// Gets the value associated with the specified key.
  174. /// </summary>
  175. /// <param name="key">The key of the value to get.</param>
  176. /// <param name="value">
  177. /// When this method returns, contains the value associated with the specified key,
  178. /// if the key is found; otherwise, the default value for the type of the value parameter.
  179. /// This parameter is passed uninitialized.</param>
  180. /// <returns>true if the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/> contains an element with the specified key; otherwise, false.</returns>
  181. public bool TryGetValue(TKey key, out TValue value) {
  182. CleanIfNeeded();
  183. WeakReference wr;
  184. if (!inner.TryGetValue(key, out wr)) {
  185. value = null;
  186. return false;
  187. }
  188. var result = (TValue) wr.Target;
  189. if (result == null) {
  190. inner.Remove(key);
  191. value = null;
  192. return false;
  193. }
  194. value = result;
  195. return true;
  196. }
  197. /// <summary>
  198. /// Gets or sets the value associated with the specified key.
  199. /// </summary>
  200. /// <param name="key">The key of the value to get or set.</param>
  201. /// <returns>
  202. /// The value associated with the specified key. If the specified key is not found, a get operation throws a <see cref="KeyNotFoundException"/>,
  203. /// and a set operation creates a new element with the specified key.
  204. /// </returns>
  205. public TValue this[TKey key] {
  206. get {
  207. TValue result;
  208. if (!TryGetValue(key, out result))
  209. throw new KeyNotFoundException();
  210. return result;
  211. }
  212. set {
  213. CleanIfNeeded();
  214. inner[key] = new WeakReference(value);
  215. }
  216. }
  217. /// <summary>
  218. /// Gets a collection containing the keys in the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.
  219. /// </summary>
  220. public ICollection<TKey> Keys {
  221. get { return inner.Keys; }
  222. }
  223. /// <summary>
  224. /// Gets a collection containing the values in the <see cref="WeakValueDictionary&lt;TKey, TValue&gt;"/>.
  225. /// </summary>
  226. public ICollection<TValue> Values {
  227. get { return new ValueCollection(this); }
  228. }
  229. #region Inner Types
  230. private sealed class ValueCollection : ICollection<TValue> {
  231. private readonly WeakValueDictionary<TKey, TValue> inner;
  232. public ValueCollection(WeakValueDictionary<TKey, TValue> dictionary) {
  233. inner = dictionary;
  234. }
  235. public IEnumerator<TValue> GetEnumerator() {
  236. return inner.Select(pair => pair.Value).GetEnumerator();
  237. }
  238. IEnumerator IEnumerable.GetEnumerator() {
  239. return GetEnumerator();
  240. }
  241. void ICollection<TValue>.Add(TValue item) {
  242. throw new NotSupportedException();
  243. }
  244. void ICollection<TValue>.Clear() {
  245. throw new NotSupportedException();
  246. }
  247. bool ICollection<TValue>.Contains(TValue item) {
  248. return inner.Any(pair => pair.Value == item);
  249. }
  250. public void CopyTo(TValue[] array, int arrayIndex) {
  251. if (array == null)
  252. throw new ArgumentNullException("array");
  253. if (arrayIndex < 0 || arrayIndex >= array.Length)
  254. throw new ArgumentOutOfRangeException("arrayIndex");
  255. if ((arrayIndex + Count) > array.Length)
  256. throw new ArgumentException(
  257. "The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.");
  258. this.ToArray().CopyTo(array, arrayIndex);
  259. }
  260. bool ICollection<TValue>.Remove(TValue item) {
  261. throw new NotSupportedException();
  262. }
  263. public int Count {
  264. get { return inner.Count; }
  265. }
  266. bool ICollection<TValue>.IsReadOnly {
  267. get { return true; }
  268. }
  269. }
  270. #endregion
  271. }
  272. }