// Needed for NET40 #if !NET_4_6 using System; using System.Collections; using System.Collections.Generic; using System.Threading; namespace LinqInternal.Collections.ThreadSafe { /// /// Represent a thread-safe lock-free hash based dictionary. /// /// The type of the value. [Serializable] internal class SafeSet : IEnumerable, ISet { private const int _defaultProbing = 1; private readonly IEqualityComparer _comparer; private Bucket _bucket; private int _probing; /// /// Initializes a new instance of the class. /// public SafeSet() : this(EqualityComparer.Default, _defaultProbing) { // Empty } /// /// Initializes a new instance of the class. /// /// The number of steps in linear probing. public SafeSet(int initialProbing) : this(EqualityComparer.Default, initialProbing) { // Empty } /// /// Initializes a new instance of the class. /// /// The value comparer. public SafeSet(IEqualityComparer comparer) : this(comparer, _defaultProbing) { // Empty } /// /// Initializes a new instance of the class. /// /// The value comparer. /// The number of steps in linear probing. public SafeSet(IEqualityComparer comparer, int initialProbing) { _comparer = comparer ?? EqualityComparer.Default; _bucket = new Bucket(); _probing = initialProbing; } public IEqualityComparer Comparer { get { return _comparer; } } public int Count { get { return _bucket.Count; } } bool ICollection.IsReadOnly { get { return false; } } public bool Add(T item) { var hashCode = _comparer.GetHashCode(item); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); T found; if (_bucket.Insert(hashCode + attempts, item, out found)) { return true; } if (_comparer.Equals(found, item)) { return false; } attempts++; } } /// /// Adds the specified value. /// /// The value. /// the value is already present public void AddNew(T value) { var hashCode = _comparer.GetHashCode(value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); T found; if (_bucket.Insert(hashCode + attempts, value, out found)) { return; } if (_comparer.Equals(found, value)) { throw new ArgumentException("the value is already present"); } attempts++; } } /// /// Removes all the elements. /// public void Clear() { _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 value is contained. /// /// The value. /// /// true if the specified value is contained; otherwise, false. /// public bool Contains(T value) { var hashCode = _comparer.GetHashCode(value); for (var attempts = 0; attempts < _probing; attempts++) { T found; if (_bucket.TryGet(hashCode + attempts, out found)) { if (_comparer.Equals(found, value)) { return true; } } } return false; } /// /// Determines whether the specified value is contained. /// /// The hash code to look for. /// The value predicate. /// /// true if the specified value is contained; otherwise, false. /// public bool Contains(int hashCode, Predicate check) { if (check == null) { throw new ArgumentNullException("check"); } for (var attempts = 0; attempts < _probing; attempts++) { T found; if (_bucket.TryGet(hashCode + attempts, out found)) { if (_comparer.GetHashCode(found) == hashCode && check(found)) { 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(T[] array, int arrayIndex) { _bucket.CopyTo(array, arrayIndex); } public void ExceptWith(IEnumerable other) { Extensions.ExceptWith(this, other); } /// /// 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(); } /// /// Gets the pairs contained in this object. /// /// The pairs contained in this object public IList GetValues() { var result = new List(_bucket.Count); foreach (var pair in _bucket) { result.Add(pair); } return result; } public void IntersectWith(IEnumerable other) { Extensions.IntersectWith(this, other); } public bool IsProperSubsetOf(IEnumerable other) { return Extensions.IsProperSubsetOf(this, other); } public bool IsProperSupersetOf(IEnumerable other) { return Extensions.IsProperSupersetOf(this, other); } public bool IsSubsetOf(IEnumerable other) { return Extensions.IsSubsetOf(this, other); } public bool IsSupersetOf(IEnumerable other) { return Extensions.IsSupersetOf(this, other); } public bool Overlaps(IEnumerable other) { return Extensions.Overlaps(this, other); } /// /// Removes the specified value. /// /// The value. /// /// true if the specified value was removed; otherwise, false. /// public bool Remove(T value) { var hashCode = _comparer.GetHashCode(value); for (var attempts = 0; attempts < _probing; attempts++) { var done = false; var result = _bucket.RemoveAt ( hashCode + attempts, found => { if (_comparer.Equals(found, value)) { done = true; return true; } return false; } ); if (done) { return result; } } return false; } /// /// Removes the specified value. /// /// The value. /// The found value that was removed. /// /// true if the specified value was removed; otherwise, false. /// public bool Remove(T value, out T previous) { var hashCode = _comparer.GetHashCode(value); for (var attempts = 0; attempts < _probing; attempts++) { var done = false; var tmp = default(T); var result = _bucket.RemoveAt ( hashCode + attempts, found => { tmp = found; if (_comparer.Equals(found, value)) { done = true; return true; } return false; } ); if (done) { previous = tmp; return result; } } previous = default(T); return false; } /// /// Removes a value by hash code and a value predicate. /// /// The hash code to look for. /// The value predicate. /// The value. /// /// true if the specified value was removed; otherwise, false. /// public bool Remove(int hashCode, Predicate check, out T value) { if (check == null) { throw new ArgumentNullException("check"); } value = default(T); for (var attempts = 0; attempts < _probing; attempts++) { var done = false; var previous = default(T); var result = _bucket.RemoveAt ( hashCode + attempts, found => { previous = found; if (_comparer.GetHashCode(found) == hashCode && check(found)) { done = true; return true; } return false; } ); if (done) { value = previous; return result; } } return false; } /// /// Removes the values where the predicate is satisfied. /// /// The predicate. /// /// The number or removed values. /// /// /// It is not guaranteed that all the values that satisfies the predicate will be removed. /// public int RemoveWhere(Predicate check) { if (check == null) { throw new ArgumentNullException("check"); } var matches = _bucket.Where(check); var count = 0; foreach (var value in matches) { if (Remove(value)) { count++; } } return count; } /// /// Removes the values where the predicate is satisfied. /// /// The predicate. /// /// An that allows to iterate over the removed values. /// /// /// It is not guaranteed that all the values that satisfies the predicate will be removed. /// public IEnumerable RemoveWhereEnumerable(Predicate check) { if (check == null) { throw new ArgumentNullException("check"); } var matches = _bucket.Where(check); foreach (var value in matches) { if (Remove(value)) { yield return value; } } } public bool SetEquals(IEnumerable other) { return Extensions.SetEquals(this, other); } public void SymmetricExceptWith(IEnumerable other) { Extensions.SymmetricExceptWith(this, other); } /// /// Tries to retrieve the value by hash code and value predicate. /// /// The hash code to look for. /// The value predicate. /// The value. /// /// true if the value was retrieved; otherwise, false. /// public bool TryGetValue(int hashCode, Predicate check, out T value) { if (check == null) { throw new ArgumentNullException("check"); } value = default(T); for (var attempts = 0; attempts < _probing; attempts++) { T found; if (_bucket.TryGet(hashCode + attempts, out found)) { if (_comparer.GetHashCode(found) == hashCode && check(found)) { value = found; return true; } } } return false; } public void UnionWith(IEnumerable other) { Extensions.UnionWith(this, other); } /// /// Returns the values where the predicate is satisfied. /// /// The predicate. /// /// An that allows to iterate over the values. /// /// /// It is not guaranteed that all the values that satisfies the predicate will be returned. /// public IEnumerable Where(Predicate check) { if (check == null) { throw new ArgumentNullException("check"); } return _bucket.Where(check); } void ICollection.Add(T item) { AddNew(item); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Attempts to add the specified value. /// /// The value. /// The value predicate to approve overwriting. /// /// true if the specified key and associated value were added; otherwise, false. /// internal bool TryAdd(T value, Predicate valueOverwriteCheck) { if (valueOverwriteCheck == null) { throw new ArgumentNullException("valueOverwriteCheck"); } var hashCode = _comparer.GetHashCode(value); var attempts = 0; while (true) { ExtendProbingIfNeeded(attempts); Predicate check = found => { if (_comparer.Equals(found, value)) { // This is the item that has been stored with the key // Throw to abort overwrite throw new ArgumentException("The item has already been added"); } // This is not the value, overwrite? return valueOverwriteCheck(found); }; try { bool isNew; // TryGetCheckSet will add if no item is found, otherwise it calls check if (_bucket.InsertOrUpdate(hashCode + attempts, value, check, out isNew)) { // It added a new item return true; } } catch (ArgumentException) { // An item with the same key has already been added return false; } attempts++; } } private void ExtendProbingIfNeeded(int attempts) { var diff = attempts - _probing; if (diff > 0) { Interlocked.Add(ref _probing, diff); } } } } #endif