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