// Needed for NET35 (ConditionalWeakTable) #if !NET_4_6 using System; using System.Collections; using System.Collections.Generic; using System.Threading; using LinqInternal.Collections.Specialized; using LinqInternal.Threading; using LinqInternal.Threading.Needles; namespace LinqInternal.Collections.ThreadSafe { // TODO: this is actually a Weak Key dictionary useful to extend objects, there could also be Weak Value dictionaries useful for caches, and fully weak dictionary useful for the combination. [System.Diagnostics.DebuggerNonUserCode] [System.Diagnostics.DebuggerDisplay("Count={Count}")] internal class WeakDictionary : IDictionary where TKey : class { private readonly KeyCollection _keyCollection; private readonly IEqualityComparer _keyComparer; private readonly ValueCollection _valueCollection; private readonly SafeDictionary, TValue> _wrapped; private readonly NeedleReservoir> _reservoir; private EventHandler _handle; public WeakDictionary() : this(null) { // Empty } public WeakDictionary(IEqualityComparer comparer) { _keyComparer = comparer ?? EqualityComparer.Default; var needleComparer = new NeedleConversionEqualityComparer, TKey>(_keyComparer); _wrapped = new SafeDictionary, TValue>(needleComparer); _keyCollection = new KeyCollection(this); _valueCollection = new ValueCollection(this); _reservoir = new NeedleReservoir>(key => new WeakNeedle(key)); } public WeakDictionary(IEqualityComparer comparer, int initialProbing) { _keyComparer = comparer ?? EqualityComparer.Default; var needleComparer = new NeedleConversionEqualityComparer, TKey>(_keyComparer); _wrapped = new SafeDictionary, TValue>(needleComparer, initialProbing); _keyCollection = new KeyCollection(this); _valueCollection = new ValueCollection(this); _reservoir = new NeedleReservoir>(key => new WeakNeedle(key)); } public bool AutoRemoveDeadItems { get { return _handle != null; } set { var handle = _handle; if (value) { var created = new EventHandler((sender, args) => RemoveDeadItems()); if (handle == null && Interlocked.CompareExchange(ref _handle, created, null) == null) { GCMonitor.Collected += created; } } else { if (handle != null && Interlocked.CompareExchange(ref _handle, null, handle) == handle) { GCMonitor.Collected -= handle; } } } } public int Count { get { return _wrapped.Count; } } bool ICollection>.IsReadOnly { get { return false; } } public IEqualityComparer KeyComparer { get { return _keyComparer; } } public ICollection Keys { get { return _keyCollection; } } public ICollection Values { get { return _valueCollection; } } protected SafeDictionary, TValue> Wrapped { get { return _wrapped; } } public TValue this[TKey key] { get { TValue value; if (TryGetValue(key, out value)) { return value; } throw new KeyNotFoundException(); } set { Set(key, value); } } void IDictionary.Add(TKey key, TValue value) { AddNew(key, value); } void ICollection>.Add(KeyValuePair item) { // No risk of dead needles here AddNew(item.Key, item.Value); } /// /// Adds the specified key and associated value. /// /// The key. /// The value. /// An item with the same key has already been added public void AddNew(TKey key, TValue value) { var needle = PrivateGetNeedle(key); try { _wrapped.AddNew(needle, input => !input.IsAlive, value); } catch (ArgumentException) { _reservoir.DonateNeedle(needle); throw; } } public TValue AddOrUpdate(TKey key, Func addValueFactory, Func updateValueFactory) { if (addValueFactory == null) { throw new ArgumentNullException("addValueFactory"); } if (updateValueFactory == null) { throw new ArgumentNullException("updateValueFactory"); } var needle = PrivateGetNeedle(key); Func, TValue, TValue> factory = (pairKey, foundValue) => { TKey foundKey; if (PrivateTryGetValue(pairKey, out foundKey)) { return updateValueFactory(foundKey, foundValue); } return addValueFactory(key); }; Func, TValue> valueFactory = input => addValueFactory(key); bool added; var result = _wrapped.AddOrUpdate ( needle, valueFactory, factory, out added ); if (!added) { _reservoir.DonateNeedle(needle); } return result; } public TValue AddOrUpdate(TKey key, TValue addValue, Func updateValueFactory) { if (updateValueFactory == null) { throw new ArgumentNullException("updateValueFactory"); } var needle = PrivateGetNeedle(key); Func, TValue, TValue> factory = (pairKey, foundValue) => { TKey foundKey; if (PrivateTryGetValue(pairKey, out foundKey)) { return updateValueFactory(foundKey, foundValue); } return addValue; }; bool added; var result = _wrapped.AddOrUpdate ( needle, addValue, factory, out added ); if (!added) { _reservoir.DonateNeedle(needle); } return result; } public TValue AddOrUpdate(TKey key, Func addValueFactory, Func updateValueFactory, out bool added) { if (addValueFactory == null) { throw new ArgumentNullException("addValueFactory"); } if (updateValueFactory == null) { throw new ArgumentNullException("updateValueFactory"); } var needle = PrivateGetNeedle(key); Func, TValue, TValue> factory = (pairKey, foundValue) => { TKey foundKey; if (PrivateTryGetValue(pairKey, out foundKey)) { return updateValueFactory(foundKey, foundValue); } return addValueFactory(key); }; Func, TValue> valueFactory = input => addValueFactory(key); var result = _wrapped.AddOrUpdate ( needle, valueFactory, factory, out added ); if (!added) { _reservoir.DonateNeedle(needle); } return result; } public TValue AddOrUpdate(TKey key, TValue addValue, Func updateValueFactory, out bool added) { if (updateValueFactory == null) { throw new ArgumentNullException("updateValueFactory"); } var needle = PrivateGetNeedle(key); Func, TValue, TValue> factory = (pairKey, foundValue) => { TKey foundKey; if (PrivateTryGetValue(pairKey, out foundKey)) { return updateValueFactory(foundKey, foundValue); } return addValue; }; var result = _wrapped.AddOrUpdate ( needle, addValue, factory, out added ); if (!added) { _reservoir.DonateNeedle(needle); } return result; } /// /// Removes all the elements. /// public void Clear() { foreach (var item in _wrapped.ClearEnumerable()) { _reservoir.DonateNeedle(item.Key); } } /// /// Removes all the elements. /// public IEnumerable> ClearEnumerable() { // No risk of dead needles here foreach (var item in _wrapped.ClearEnumerable()) { TKey foundKey; if (PrivateTryGetValue(item.Key, out foundKey)) { var value = item.Value; yield return new KeyValuePair(foundKey, value); _reservoir.DonateNeedle(item.Key); } } } bool ICollection>.Contains(KeyValuePair item) { // No risk of dead needles here Predicate> check = input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return _keyComparer.Equals(foundKey, item.Key); } return false; }; return _wrapped.ContainsKey ( _keyComparer.GetHashCode(item.Key), check, input => EqualityComparer.Default.Equals(input, item.Value) ); } /// /// Determines whether the specified key is contained. /// /// The key. /// /// true if the specified key is contained; otherwise, false. /// public bool ContainsKey(TKey key) { return _wrapped.ContainsKey ( _keyComparer.GetHashCode(key), input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return _keyComparer.Equals(foundKey, key); } return false; } ); } /// /// Determines whether the specified key is contained. /// /// The hash code to look for. /// The key predicate. /// /// true if the specified key is contained; otherwise, false. /// /// is null. public bool ContainsKey(int hashCode, Predicate keyCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } return _wrapped.ContainsKey ( hashCode, input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return keyCheck(foundKey); } return false; } ); } /// /// Determines whether the specified key is contained. /// /// The hash code to look for. /// The key predicate. /// The value predicate. /// /// true if the specified key is contained; otherwise, false. /// /// is null. public bool ContainsKey(int hashCode, Predicate keyCheck, Predicate valueCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } if (valueCheck == null) { throw new ArgumentNullException("valueCheck"); } return _wrapped.ContainsKey ( hashCode, input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return keyCheck(foundKey); } return false; }, valueCheck ); } /// /// Copies the items to a compatible one-dimensional array, starting at the specified index of the target array. /// /// The array. /// Index of the array. /// array /// arrayIndex;Non-negative number is required. /// array;The array can not contain the number of elements. public void CopyTo(KeyValuePair[] array, int arrayIndex) { if (array == null) { throw new ArgumentNullException("array"); } if (arrayIndex < 0) { throw new ArgumentOutOfRangeException("arrayIndex", "Non-negative number is required."); } if (_wrapped.Count > array.Length - arrayIndex) { throw new ArgumentException("The array can not contain the number of elements.", "array"); } GetPairs().CopyTo(array, arrayIndex); } /// /// Returns an that allows to iterate through the collection. /// /// /// An object that can be used to iterate through the collection. /// public IEnumerator> GetEnumerator() { // No risk of dead needles here foreach (var pair in _wrapped) { TKey foundKey; if (PrivateTryGetValue(pair.Key, out foundKey)) { yield return new KeyValuePair(foundKey, pair.Value); } } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public TValue GetOrAdd(TKey key, TValue value) { var needle = PrivateGetNeedle(key); TValue result; if (!_wrapped.TryGetOrAdd(needle, input => !input.IsAlive, value, out result)) { _reservoir.DonateNeedle(needle); } return result; } public TValue GetOrAdd(TKey key, Func valueFactory) { if (valueFactory == null) { throw new ArgumentNullException("valueFactory"); } var needle = PrivateGetNeedle(key); TValue result; TKey foundKey; Func, TValue, TValue> factory = (pairKey, foundValue) => result = valueFactory(PrivateTryGetValue(pairKey, out foundKey) ? foundKey : key); if (_wrapped.TryGetOrAdd(needle, () => valueFactory(key), factory, out result)) { return result; } _reservoir.DonateNeedle(needle); return result; } /// /// Gets the pairs contained in this object. /// public IList> GetPairs() { // No risk of dead needles here var result = new List>(_wrapped.Count); foreach (var pair in _wrapped) { TKey foundKey; if (PrivateTryGetValue(pair.Key, out foundKey)) { var value = pair.Value; result.Add(new KeyValuePair(foundKey, value)); } } return result; } /// /// Removes a key by hash code, key predicate and value predicate. /// /// The key. /// The value predicate. /// The value. /// /// true if the specified key was removed; otherwise, false. /// public bool Remove(TKey key, Predicate valueCheck, out TValue value) { return _wrapped.Remove ( _keyComparer.GetHashCode(key), input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return _keyComparer.Equals(foundKey, key); } return false; }, valueCheck, out value ); } /// /// Removes the specified key. /// /// The key. /// /// true if the specified key was removed; otherwise, false. /// public bool Remove(TKey key) { TValue value; return _wrapped.Remove ( _keyComparer.GetHashCode(key), input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return _keyComparer.Equals(foundKey, key); } return false; }, out value ); } /// /// Removes the specified key. /// /// The key. /// The value. /// /// true if the specified key was removed; otherwise, false. /// public bool Remove(TKey key, out TValue value) { return _wrapped.Remove ( _keyComparer.GetHashCode(key), input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return _keyComparer.Equals(foundKey, key); } return false; }, out value ); } /// /// Removes a key by hash code and a key predicate. /// /// The hash code to look for. /// The key predicate. /// The value. /// /// true if the specified key was removed; otherwise, false. /// /// is null. public bool Remove(int hashCode, Predicate keyCheck, out TValue value) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } return _wrapped.Remove ( hashCode, input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return keyCheck.Invoke(foundKey); } return false; }, out value ); } /// /// Removes a key by hash code, key predicate and value predicate. /// /// The hash code to look for. /// The key predicate. /// The value predicate. /// The value. /// /// true if the specified key was removed; otherwise, false. /// /// is null. public bool Remove(int hashCode, Predicate keyCheck, Predicate valueCheck, out TValue value) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } if (valueCheck == null) { throw new ArgumentNullException("valueCheck"); } return _wrapped.Remove ( hashCode, input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return keyCheck.Invoke(foundKey); } return false; }, valueCheck, out value ); } bool ICollection>.Remove(KeyValuePair item) { // No risk of dead needles here Predicate> check = input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return _keyComparer.Equals(foundKey, item.Key); } return false; }; TValue found; return _wrapped.Remove ( _keyComparer.GetHashCode(item.Key), check, input => EqualityComparer.Default.Equals(input, item.Value), out found ); } public int RemoveDeadItems() { return _wrapped.RemoveWhereKey(key => !key.IsAlive); } /// /// Removes the keys and associated values where the key satisfies the predicate. /// /// The predicate. /// /// The number or removed pairs of keys and associated values. /// /// /// It is not guaranteed that all the pairs of keys and associated values that satisfies the predicate will be removed. /// /// is null. public int RemoveWhereKey(Predicate keyCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } return _wrapped.RemoveWhereKey ( input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return keyCheck.Invoke(foundKey); } return false; } ); } /// /// Removes the keys and associated values where the key satisfies the predicate. /// /// The predicate. /// /// An that allows to iterate over the values of the removed pairs. /// /// /// It is not guaranteed that all the pairs of keys and associated values that satisfies the predicate will be removed. /// /// is null. public IEnumerable RemoveWhereKeyEnumerable(Predicate keyCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } return _wrapped.RemoveWhereKeyEnumerable ( input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return keyCheck.Invoke(foundKey); } return false; } ); } /// /// Removes the keys and associated values where the value satisfies the predicate. /// /// The predicate. /// /// The number or removed pairs of keys and associated values. /// /// /// It is not guaranteed that all the pairs of keys and associated values that satisfies the predicate will be removed. /// /// is null. public int RemoveWhereValue(Predicate valueCheck) { if (valueCheck == null) { throw new ArgumentNullException("valueCheck"); } return _wrapped.RemoveWhereValue(valueCheck); } /// /// Removes the keys and associated values where the value satisfies the predicate. /// /// The predicate. /// /// An that allows to iterate over the values of the removed pairs. /// /// /// It is not guaranteed that all the pairs of keys and associated values that satisfies the predicate will be removed. /// /// is null. public IEnumerable RemoveWhereValueEnumerable(Predicate valueCheck) { if (valueCheck == null) { throw new ArgumentNullException("valueCheck"); } return _wrapped.RemoveWhereValueEnumerable(valueCheck); } /// /// Sets the value associated with the specified key. /// /// The key. /// The value. public void Set(TKey key, TValue value) { var needle = PrivateGetNeedle(key); _wrapped.Set(needle, input => !input.IsAlive, value); } /// /// Sets the value associated with the specified key. /// /// The key. /// The value. /// if set to true the item value was set. public void Set(TKey key, TValue value, out bool isNew) { var needle = PrivateGetNeedle(key); _wrapped.Set(needle, input => !input.IsAlive, value, out isNew); } /// /// Attempts to add the specified key and associated value. The value is added if the key is not found. /// /// The key. /// The value. /// /// true if the specified key and associated value were added; otherwise, false. /// public bool TryAdd(TKey key, TValue value) { var needle = PrivateGetNeedle(key); if (_wrapped.TryAdd(needle, input => !input.IsAlive, value)) { return true; } _reservoir.DonateNeedle(needle); return false; } /// /// Attempts to add the specified key and associated value. The value is added if the key is not found. /// /// The key. /// The value. /// The stored pair independently of success. /// /// true if the specified key and associated value were added; otherwise, false. /// public bool TryAdd(TKey key, TValue value, out KeyValuePair stored) { // No risk of dead needles here var needle = PrivateGetNeedle(key); Predicate> check = found => { TKey foundKey; if (PrivateTryGetValue(found, out foundKey)) { // Keeping the found key alive // If we found a key, key will be the key found // If we didn't key will be the key added // So, either way key is the key that is stored // By having it here, we don't need to read _stored.Key key = foundKey; return false; } return true; }; KeyValuePair, TValue> storedPair; var result = _wrapped.TryAdd(needle, check, value, out storedPair); if (!result) { _reservoir.DonateNeedle(needle); } stored = new KeyValuePair(key, storedPair.Value); return result; } public bool TryGetOrAdd(TKey key, Func valueFactory, out TValue stored) { if (valueFactory == null) { throw new ArgumentNullException("valueFactory"); } var needle = PrivateGetNeedle(key); TKey foundKey; Func, TValue, TValue> factory = (pairKey, foundValue) => valueFactory(PrivateTryGetValue(pairKey, out foundKey) ? foundKey : key); if (_wrapped.TryGetOrAdd(needle, () => valueFactory(key), factory, out stored)) { return true; } _reservoir.DonateNeedle(needle); return false; } public bool TryGetOrAdd(TKey key, TValue value, out TValue stored) { var needle = PrivateGetNeedle(key); if (_wrapped.TryGetOrAdd(needle, input => !input.IsAlive, value, out stored)) { return true; } _reservoir.DonateNeedle(needle); return false; } /// /// Tries to retrieve the value associated with the specified key. /// /// The key. /// The value. /// /// true if the value was retrieved; otherwise, false. /// public bool TryGetValue(TKey key, out TValue value) { Predicate> check = found => { TKey foundKey; if (PrivateTryGetValue(found, out foundKey)) { return _keyComparer.Equals(key, foundKey); } return false; }; return _wrapped.TryGetValue(_keyComparer.GetHashCode(key), check, out value); } /// /// Tries to retrieve the value by hash code and key predicate. /// /// The hash code to look for. /// The key predicate. /// The value. /// /// true if the value was retrieved; otherwise, false. /// /// is null. public bool TryGetValue(int hashCode, Predicate keyCheck, out TValue value) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } Predicate> check = found => { TKey foundKey; if (PrivateTryGetValue(found, out foundKey)) { return keyCheck(foundKey); } return false; }; return _wrapped.TryGetValue(hashCode, check, out value); } public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) { var needle = PrivateGetNeedle(key); if (_wrapped.TryUpdate(needle, newValue, comparisonValue)) { return true; } _reservoir.DonateNeedle(needle); return false; } public bool TryUpdate(TKey key, TValue newValue, Predicate valueCheck) { var needle = PrivateGetNeedle(key); if (_wrapped.TryUpdate(needle, newValue, valueCheck)) { return true; } _reservoir.DonateNeedle(needle); return false; } /// /// Returns the values where the key satisfies the predicate. /// /// The predicate. /// /// An that allows to iterate over the values of the matched pairs. /// /// /// It is not guaranteed that all the pairs of keys and associated values that satisfies the predicate will be returned. /// /// is null. public IEnumerable Where(Predicate keyCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } return _wrapped.Where ( input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return keyCheck(foundKey); } return false; } ); } /// /// Adds the specified key and associated value. /// /// The key. /// The key predicate to approve overwriting. /// The value. /// An item with the same key has already been added internal void AddNew(TKey key, Predicate keyOverwriteCheck, TValue value) { // NOTICE this method has no null check var needle = PrivateGetNeedle(key); try { _wrapped.AddNew ( needle, input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return keyOverwriteCheck(foundKey); } return true; }, value ); } catch (ArgumentException) { _reservoir.DonateNeedle(needle); throw; } } internal TValue GetOrAdd(TKey key, Predicate keyOverwriteCheck, TValue value) { // NOTICE this method has no null check var needle = PrivateGetNeedle(key); TValue stored; if ( !_wrapped.TryGetOrAdd ( needle, input => { TKey foundKey; if (PrivateTryGetValue(input, out foundKey)) { return keyOverwriteCheck(foundKey); } return true; }, value, out stored ) ) { _reservoir.DonateNeedle(needle); } return stored; } /// /// Sets the value associated with the specified key. /// /// The key. /// The key predicate to approve overwriting. /// The value. internal void Set(TKey key, Predicate keyOverwriteCheck, TValue value) { // NOTICE this method has no null check var needle = PrivateGetNeedle(key); TKey foundKey; _wrapped.Set ( needle, input => { if (PrivateTryGetValue(input, out foundKey)) { return keyOverwriteCheck(foundKey); } return true; }, value ); } /// /// Sets the value associated with the specified key. /// /// The key. /// The key predicate to approve overwriting. /// The value. /// if set to true the item value was set. internal void Set(TKey key, Predicate keyOverwriteCheck, TValue value, out bool isNew) { // NOTICE this method has no null check var needle = PrivateGetNeedle(key); TKey foundKey; _wrapped.Set ( needle, input => { if (PrivateTryGetValue(input, out foundKey)) { return keyOverwriteCheck(foundKey); } return true; }, value, out isNew ); } /// /// Attempts to add the specified key and associated value. The value is added if the key is not found. /// /// The key. /// The key predicate to approve overwriting. /// The value. /// /// true if the specified key and associated value were added; otherwise, false. /// internal bool TryAdd(TKey key, Predicate keyOverwriteCheck, TValue value) { // NOTICE this method has no null check var needle = PrivateGetNeedle(key); TKey foundKey; if ( _wrapped.TryAdd ( needle, input => { if (PrivateTryGetValue(input, out foundKey)) { return keyOverwriteCheck(foundKey); } return true; }, value ) ) { return true; } _reservoir.DonateNeedle(needle); return false; } internal bool TryGetOrAdd(TKey key, Predicate keyOverwriteCheck, TValue value, out TValue stored) { // NOTICE this method has no null check var needle = PrivateGetNeedle(key); TKey foundKey; if ( _wrapped.TryGetOrAdd ( needle, input => { if (PrivateTryGetValue(input, out foundKey)) { return keyOverwriteCheck(foundKey); } return true; }, value, out stored ) ) { return true; } _reservoir.DonateNeedle(needle); return false; } protected bool Contains(KeyValuePair item) { return ((ICollection>)this).Contains(item); } private WeakNeedle PrivateGetNeedle(TKey key) { return _reservoir.GetNeedle(key); } private static bool PrivateTryGetValue(WeakNeedle needle, out TKey foundKey) { return needle.TryGetValue(out foundKey); } } } #endif