// Needed for NET40 #if !NET_4_6 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; using LinqInternal.Collections.Specialized; namespace LinqInternal.Collections.ThreadSafe { /// /// Represent a thread-safe lock-free hash based dictionary. /// /// The type of the key. /// The type of the value. /// /// Consider wrapping this class to implement or any other desired interface. /// [Serializable] internal sealed partial class SafeDictionary : IDictionary { private const int _defaultProbing = 1; private readonly KeyCollection _keyCollection; private readonly IEqualityComparer _keyComparer; private readonly ValueCollection _valueCollection; private readonly IEqualityComparer _valueComparer; private Bucket> _bucket; private int _probing; /// /// Initializes a new instance of the class. /// public SafeDictionary() : this(EqualityComparer.Default, _defaultProbing) { // Empty } /// /// Initializes a new instance of the class. /// /// The number of steps in linear probing. public SafeDictionary(int initialProbing) : this(EqualityComparer.Default, initialProbing) { // Empty } /// /// Initializes a new instance of the class. /// /// The key comparer. public SafeDictionary(IEqualityComparer comparer) : this(comparer, _defaultProbing) { // Empty } /// /// Initializes a new instance of the class. /// /// The key comparer. /// The number of steps in linear probing. public SafeDictionary(IEqualityComparer comparer, int initialProbing) { _keyComparer = comparer ?? EqualityComparer.Default; _valueComparer = EqualityComparer.Default; _bucket = new Bucket>(); _probing = initialProbing; _keyCollection = new KeyCollection(this); _valueCollection = new ValueCollection(this); } public int Count { get { return _bucket.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; } } public TValue this[TKey key] { get { TValue value; if (TryGetValue(key, out value)) { return value; } throw new KeyNotFoundException(); } set { Set(key, 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 insertPair = new KeyValuePair(key, value); var hashCode = GetHashCode(key); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); KeyValuePair found; if (_bucket.Insert(hashCode + attempts, insertPair, out found)) { return; } if (_keyComparer.Equals(found.Key, key)) { throw new ArgumentException("An item with the same key has already been added", "key"); } attempts++; } } /// /// Removes all the elements. /// public void Clear() { Interlocked.Exchange(ref _bucket, _bucket = new Bucket>()); } /// /// Removes all the elements. /// /// Returns the removed pairs. public IEnumerable> ClearEnumerable() { return Interlocked.Exchange(ref _bucket, _bucket = new Bucket>()); } /// /// Determines whether the specified key is contained. /// /// The key. /// /// true if the specified key is contained; otherwise, false. /// public bool ContainsKey(TKey key) { var hashCode = GetHashCode(key); for (var attempts = 0; attempts < _probing; attempts++) { KeyValuePair found; if (_bucket.TryGet(hashCode + attempts, out found)) { if (_keyComparer.Equals(found.Key, key)) { return true; } } } 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. /// public bool ContainsKey(int hashCode, Predicate keyCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } for (var attempts = 0; attempts < _probing; attempts++) { KeyValuePair found; if (_bucket.TryGet(hashCode + attempts, out found)) { if (GetHashCode(found.Key) == hashCode && keyCheck(found.Key)) { return true; } } } 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. /// public bool ContainsKey(int hashCode, Predicate keyCheck, Predicate valueCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } if (valueCheck == null) { throw new ArgumentNullException("valueCheck"); } for (var attempts = 0; attempts < _probing; attempts++) { KeyValuePair found; if (_bucket.TryGet(hashCode + attempts, out found)) { if (GetHashCode(found.Key) == hashCode && keyCheck(found.Key) && valueCheck(found.Value)) { return true; } } } return false; } /// /// 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) { _bucket.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() { return _bucket.GetEnumerator(); } public TValue GetOrAdd(TKey key, Func valueFactory) { if (valueFactory == null) { throw new ArgumentNullException("valueFactory"); } var hashCode = GetHashCode(key); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); KeyValuePair storedPair; if (_bucket.TryGetOrInsert(hashCode + attempts, () => new KeyValuePair(key, valueFactory(key)), out storedPair)) { return storedPair.Value; } if (_keyComparer.Equals(storedPair.Key, key)) { return storedPair.Value; } attempts++; } } public TValue GetOrAdd(TKey key, TValue value) { var hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); KeyValuePair storedPair; if (_bucket.TryGetOrInsert(hashCode + attempts, insertPair, out storedPair)) { return storedPair.Value; } if (_keyComparer.Equals(storedPair.Key, key)) { return storedPair.Value; } attempts++; } } /// /// Gets the pairs contained in this object. /// /// The pairs contained in this object public IList> GetPairs() { var result = new List>(_bucket.Count); result.AddRange(_bucket); return result; } void ICollection>.Add(KeyValuePair item) { AddNew(item.Key, item.Value); } bool ICollection>.Contains(KeyValuePair item) { var hashCode = GetHashCode(item.Key); for (var attempts = 0; attempts < _probing; attempts++) { KeyValuePair found; if (_bucket.TryGet(hashCode + attempts, out found)) { if (_keyComparer.Equals(found.Key, item.Key)) { if (_valueComparer.Equals(found.Value, item.Value)) { return true; } return false; } } } return false; } bool ICollection>.Remove(KeyValuePair item) { var hashCode = GetHashCode(item.Key); for (var attempts = 0; attempts < _probing; attempts++) { var done = false; var result = _bucket.RemoveAt ( hashCode + attempts, found => { if (_keyComparer.Equals(found.Key, item.Key)) { done = true; if (_valueComparer.Equals(found.Value, item.Value)) { return true; } } return false; } ); if (done) { return result; } } return false; } void IDictionary.Add(TKey key, TValue value) { AddNew(key, value); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Removes the specified key. /// /// The key. /// /// true if the specified key was removed; otherwise, false. /// public bool Remove(TKey key) { var hashCode = GetHashCode(key); for (var attempts = 0; attempts < _probing; attempts++) { var done = false; Predicate> check = found => { if (_keyComparer.Equals(found.Key, key)) { done = true; return true; } return false; }; var result = _bucket.RemoveAt ( hashCode + attempts, check ); if (done) { return result; } } return false; } /// /// 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) { value = default(TValue); var hashCode = GetHashCode(key); for (var attempts = 0; attempts < _probing; attempts++) { var done = false; var previous = default(KeyValuePair); Predicate> check = found => { previous = found; if (_keyComparer.Equals(found.Key, key)) { done = true; return true; } return false; }; var result = _bucket.RemoveAt ( hashCode + attempts, check ); if (done) { value = previous.Value; return result; } } return false; } /// /// 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. /// public bool Remove(int hashCode, Predicate keyCheck, out TValue value) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } value = default(TValue); for (var attempts = 0; attempts < _probing; attempts++) { var done = false; var previous = default(KeyValuePair); Predicate> check = found => { previous = found; if (GetHashCode(found.Key) == hashCode && keyCheck(found.Key)) { done = true; return true; } return false; }; var result = _bucket.RemoveAt ( hashCode + attempts, check ); if (done) { value = previous.Value; return result; } } return false; } /// /// 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) { if (valueCheck == null) { throw new ArgumentNullException("valueCheck"); } value = default(TValue); var hashCode = GetHashCode(key); for (var attempts = 0; attempts < _probing; attempts++) { var done = false; var previous = default(KeyValuePair); Predicate> check = found => { previous = found; if (_keyComparer.Equals(found.Key, key)) { done = true; if (valueCheck(found.Value)) { return true; } } return false; }; var result = _bucket.RemoveAt ( hashCode + attempts, check ); if (done) { value = previous.Value; return result; } } return false; } /// /// 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. /// 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"); } value = default(TValue); for (var attempts = 0; attempts < _probing; attempts++) { var done = false; var previous = default(KeyValuePair); Predicate> check = found => { previous = found; if (GetHashCode(found.Key) == hashCode && keyCheck(found.Key)) { done = true; if (valueCheck(found.Value)) { return true; } } return false; }; var result = _bucket.RemoveAt ( hashCode + attempts, check ); if (done) { value = previous.Value; return result; } } return false; } /// /// 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. /// public int RemoveWhereKey(Predicate keyCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } var matches = _bucket.Where(pair => keyCheck(pair.Key)); return matches.Count(pair => Remove(pair.Key)); } /// /// 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. /// public IEnumerable RemoveWhereKeyEnumerable(Predicate keyCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } var matches = _bucket.Where(pair => keyCheck(pair.Key)); return from pair in matches where Remove(pair.Key) select pair.Value; } /// /// 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. /// public int RemoveWhereValue(Predicate valueCheck) { if (valueCheck == null) { throw new ArgumentNullException("valueCheck"); } var matches = _bucket.Where(pair => valueCheck(pair.Value)); return matches.Count(pair => Remove(pair.Key)); } /// /// 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. /// public IEnumerable RemoveWhereValueEnumerable(Predicate valueCheck) { if (valueCheck == null) { throw new ArgumentNullException("valueCheck"); } var matches = _bucket.Where(pair => valueCheck(pair.Value)); return from pair in matches where Remove(pair.Key) select pair.Value; } /// /// Sets the value associated with the specified key. /// /// The key. /// The value. public void Set(TKey key, TValue value) { var hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); bool isNew; if (_bucket.InsertOrUpdate(hashCode + attempts, insertPair, found => _keyComparer.Equals(found.Key, key), out isNew)) { return; } attempts++; } } /// /// 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 hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); if (_bucket.InsertOrUpdate(hashCode + attempts, insertPair, found => _keyComparer.Equals(found.Key, key), out isNew)) { return; } attempts++; } } /// /// 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 hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); KeyValuePair found; if (_bucket.Insert(hashCode + attempts, insertPair, out found)) { return true; } if (_keyComparer.Equals(found.Key, key)) { return false; } attempts++; } } /// /// 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) { var hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); if (_bucket.Insert(hashCode + attempts, insertPair, out stored)) { stored = insertPair; return true; } if (_keyComparer.Equals(stored.Key, key)) { return false; } attempts++; } } public bool TryGetOrAdd(TKey key, TValue value, out TValue stored) { var hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); KeyValuePair storedPair; if (_bucket.TryGetOrInsert(hashCode + attempts, insertPair, out storedPair)) { stored = storedPair.Value; return true; } if (_keyComparer.Equals(storedPair.Key, key)) { stored = storedPair.Value; return false; } attempts++; } } public bool TryGetOrAdd(TKey key, Func valueFactory, out TValue stored) { if (valueFactory == null) { throw new ArgumentException("valueFactory"); } var hashCode = GetHashCode(key); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); KeyValuePair storedPair; if (_bucket.TryGetOrInsert(hashCode + attempts, () => new KeyValuePair(key, valueFactory(key)), out storedPair)) { stored = storedPair.Value; return true; } if (_keyComparer.Equals(storedPair.Key, key)) { stored = storedPair.Value; return false; } attempts++; } } /// /// 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) { value = default(TValue); var hashCode = GetHashCode(key); for (var attempts = 0; attempts < _probing; attempts++) { KeyValuePair found; if (_bucket.TryGet(hashCode + attempts, out found)) { if (_keyComparer.Equals(found.Key, key)) { value = found.Value; return true; } } } return false; } public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) { var hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, newValue); for (var attempts = 0; attempts < _probing; attempts++) { var keyMatch = false; ExtendProbingIfNeeded(attempts); Predicate> check = found => { keyMatch = _keyComparer.Equals(found.Key, key); return keyMatch && _valueComparer.Equals(found.Value, comparisonValue); }; if (_bucket.Update(hashCode + attempts, insertPair, check)) { return true; } if (keyMatch) { return false; } } return false; } public bool TryUpdate(TKey key, TValue newValue, Predicate valueCheck) { if (valueCheck == null) { throw new ArgumentNullException("valueCheck"); } var hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, newValue); for (var attempts = 0; attempts < _probing; attempts++) { var keyMatch = false; ExtendProbingIfNeeded(attempts); Predicate> check = found => { keyMatch = _keyComparer.Equals(found.Key, key); return keyMatch && valueCheck(found.Value); }; bool isEmpty; if (_bucket.Update(hashCode + attempts, _ => insertPair, check, out isEmpty)) { return true; } if (keyMatch) { return false; } } 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. /// public IEnumerable Where(Predicate keyCheck) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } var matches = _bucket.Where(pair => keyCheck(pair.Key)); return matches.Select(pair => pair.Value); } /// /// 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 hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); Predicate> check = found => { if (_keyComparer.Equals(found.Key, key)) { // This is the item that has been stored with the key // Throw to abort overwrite throw CreateKeyArgumentException(null); // This exception will buble up to the context where "key" is an argument. } // This is not the key, overwrite? return keyOverwriteCheck(found.Key); }; // No try-catch - let the exception go. bool isNew; // InsertOrUpdate will add if no item is found, otherwise it calls check _bucket.InsertOrUpdate(hashCode + attempts, insertPair, check, out isNew); if (isNew) { // It added a new item return; } attempts++; } } /// /// 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 hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); bool isNew; Predicate> check = found => _keyComparer.Equals(found.Key, key) || keyOverwriteCheck(found.Key); if (_bucket.InsertOrUpdate(hashCode + attempts, insertPair, check, out isNew)) { return; } attempts++; } } /// /// 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 hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); Predicate> check = found => _keyComparer.Equals(found.Key, key) || keyOverwriteCheck(found.Key); if (_bucket.InsertOrUpdate(hashCode + attempts, insertPair, check, out isNew)) { return; } attempts++; } } /// /// 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 hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); Predicate> check = found => { if (_keyComparer.Equals(found.Key, key)) { // This is the item that has been stored with the key // Throw to abort overwrite throw CreateKeyArgumentException(null); // This exception will buble up to the context where "key" is an argument. } // This is not the key, overwrite? return keyOverwriteCheck(found.Key); }; try { bool isNew; // InsertOrUpdate will add if no item is found, otherwise it calls check _bucket.InsertOrUpdate(hashCode + attempts, insertPair, check, out isNew); if (isNew) { // It added a new item return true; } } catch (ArgumentException) { // An item with the same key has already been added return false; } attempts++; } } internal bool TryGetOrAdd(TKey key, Predicate keyOverwriteCheck, TValue value, out TValue stored) { // NOTICE this method has no null check var hashCode = GetHashCode(key); var insertPair = new KeyValuePair(key, value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); Predicate> check = found => { if (_keyComparer.Equals(found.Key, key)) { // This is the item that has been stored with the key value = found.Value; // Throw to abort overwrite throw CreateKeyArgumentException(null); // This exception will buble up to the context where "key" is an argument. } // This is not the key, overwrite? return keyOverwriteCheck(found.Key); }; try { bool isNew; // InsertOrUpdate will add if no item is found, otherwise it calls check _bucket.InsertOrUpdate(hashCode + attempts, insertPair, check, out isNew); if (isNew) { // It added a new item stored = value; return true; } } catch (ArgumentException) { // An item with the same key has already been added // Return it stored = value; return false; } attempts++; } } private void ExtendProbingIfNeeded(int attempts) { var diff = 1 + attempts - _probing; if (diff > 0) { Interlocked.Add(ref _probing, diff); } } private int GetHashCode(TKey key) { var hashCode = _keyComparer.GetHashCode(key); if (hashCode < 0) { hashCode = -hashCode; } // -int.MinValue == int.MinValue if (hashCode < 0) { hashCode = 0; } return hashCode; } } internal sealed partial class SafeDictionary { public TValue AddOrUpdate(TKey key, Func addValueFactory, Func updateValueFactory) { if (addValueFactory == null) { throw new ArgumentNullException("addValueFactory"); } if (updateValueFactory == null) { throw new ArgumentNullException("updateValueFactory"); } var hashCode = GetHashCode(key); var attempts = 0; var insertPair = default(KeyValuePair); var updatePair = default(KeyValuePair); while (true) { ExtendProbingIfNeeded(attempts); bool isNew; Func> itemFactory = () => { return insertPair = new KeyValuePair(key, addValueFactory(key)); }; Func, KeyValuePair> itemUpdateFactory = found => { return updatePair = new KeyValuePair(key, updateValueFactory(found.Key, found.Value)); }; Predicate> check = found => _keyComparer.Equals(key, found.Key); var result = _bucket.InsertOrUpdate ( hashCode + attempts, itemFactory, itemUpdateFactory, check, out isNew ); if (result) { return isNew ? insertPair.Value : updatePair.Value; } attempts++; } } public TValue AddOrUpdate(TKey key, TValue addValue, Func updateValueFactory) { if (updateValueFactory == null) { throw new ArgumentNullException("updateValueFactory"); } var hashCode = GetHashCode(key); var attempts = 0; var insertPair = new KeyValuePair(key, addValue); var updatePair = default(KeyValuePair); while (true) { ExtendProbingIfNeeded(attempts); bool isNew; Func, KeyValuePair> updateFactory = found => { return updatePair = new KeyValuePair(key, updateValueFactory(found.Key, found.Value)); }; Predicate> check = found => _keyComparer.Equals(key, found.Key); var result = _bucket.InsertOrUpdate ( hashCode + attempts, insertPair, updateFactory, check, out isNew ); if (result) { return isNew ? insertPair.Value : updatePair.Value; } attempts++; } } public TValue AddOrUpdate(TKey key, Func addValueFactory, Func updateValueFactory, out bool isNew) { if (addValueFactory == null) { throw new ArgumentNullException("addValueFactory"); } if (updateValueFactory == null) { throw new ArgumentNullException("updateValueFactory"); } var hashCode = GetHashCode(key); var attempts = 0; var insertPair = default(KeyValuePair); var updatePair = default(KeyValuePair); while (true) { ExtendProbingIfNeeded(attempts); Func> valueFactory = () => { return insertPair = new KeyValuePair(key, addValueFactory(key)); }; Func, KeyValuePair> updateFactory = found => { return updatePair = new KeyValuePair(key, updateValueFactory(found.Key, found.Value)); }; Predicate> check = found => _keyComparer.Equals(key, found.Key); var result = _bucket.InsertOrUpdate ( hashCode + attempts, valueFactory, updateFactory, check, out isNew ); if (result) { return isNew ? insertPair.Value : updatePair.Value; } attempts++; } } public TValue AddOrUpdate(TKey key, TValue addValue, Func updateValueFactory, out bool isNew) { if (ReferenceEquals(updateValueFactory, null)) { throw new ArgumentNullException("updateValueFactory"); } var hashCode = GetHashCode(key); var attempts = 0; var insertPair = new KeyValuePair(key, addValue); var updatePair = default(KeyValuePair); while (true) { ExtendProbingIfNeeded(attempts); Func, KeyValuePair> updateFactory = found => { return updatePair = new KeyValuePair(key, updateValueFactory(found.Key, found.Value)); }; Predicate> check = found => _keyComparer.Equals(key, found.Key); var result = _bucket.InsertOrUpdate ( hashCode + attempts, insertPair, updateFactory, check, out isNew ); if (result) { return isNew ? insertPair.Value : updatePair.Value; } attempts++; } } /// /// 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. /// public bool TryGetValue(int hashCode, Predicate keyCheck, out TValue value) { if (keyCheck == null) { throw new ArgumentNullException("keyCheck"); } value = default(TValue); for (var attempts = 0; attempts < _probing; attempts++) { KeyValuePair found; if (_bucket.TryGet(hashCode + attempts, out found)) { if (GetHashCode(found.Key) == hashCode && keyCheck(found.Key)) { value = found.Value; return true; } } } return false; } /// /// 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. /// The stored pair independently of success. /// /// true if the specified key and associated value were added; otherwise, false. /// internal bool TryAdd(TKey key, Predicate keyOverwriteCheck, TValue value, out KeyValuePair stored) { // NOTICE this method has no null check var hashCode = GetHashCode(key); var created = new KeyValuePair(key, value); var attempts = 0; while (true) { var foundPair = created; ExtendProbingIfNeeded(attempts); Predicate> check = found => { foundPair = found; if (_keyComparer.Equals(foundPair.Key, key)) { // This is the item that has been stored with the key // Throw to abort overwrite throw CreateKeyArgumentException(null); // This exception will buble up to the context where "key" is an argument. } // This is not the key, overwrite? return keyOverwriteCheck(foundPair.Key); }; try { bool isNew; // InsertOrUpdate will add if no item is found, otherwise it calls check _bucket.InsertOrUpdate(hashCode + attempts, created, check, out isNew); if (isNew) { // It added a new item stored = created; return true; } } catch (ArgumentException) { // An item with the same key has already been added stored = foundPair; return false; } attempts++; } } internal bool TryGetOrAdd(TKey key, Func addValueFactory, Func updateValueFactory, out TValue stored) { // NOTICE this method has no null check var hashCode = GetHashCode(key); var attempts = 0; while (true) { var value = default(TValue); ExtendProbingIfNeeded(attempts); Func> itemFactory = () => new KeyValuePair(key, value = addValueFactory()); Func, KeyValuePair> itemUpdateFactory = found => { if (_keyComparer.Equals(found.Key, key)) { // This is the item that has been stored with the key value = found.Value; // Throw to abort overwrite throw CreateKeyArgumentException(null); // This exception will buble up to the context where "key" is an argument. } value = updateValueFactory(found.Key, found.Value); return new KeyValuePair(key, value); }; try { bool isNew; _bucket.InsertOrUpdate(hashCode + attempts, itemFactory, itemUpdateFactory, out isNew); if (isNew) { // It added a new item stored = value; return true; } } catch (ArgumentException) { // An item with the same key has already been added // Return it stored = value; return false; } attempts++; } } private static ArgumentException CreateKeyArgumentException(object key) { GC.KeepAlive(key); return new ArgumentException("An item with the same key has already been added", "key"); } } } #endif