#if !UNITY_WSA
|
using System;
|
using System.Collections;
|
using System.Collections.Generic;
|
using System.Collections.ObjectModel;
|
using System.Diagnostics;
|
using System.Runtime.InteropServices;
|
using System.Runtime.Serialization;
|
using System.Threading;
|
|
namespace TcpServer
|
{
|
|
/// <summary>
|
/// Represents a thread-safe collection of keys and values.
|
/// </summary>
|
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
|
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
|
/// <remarks>
|
/// All public and protected members of <see cref="ConcurrentDictionary{TKey,TValue}"/> are thread-safe and may be used
|
/// concurrently from multiple threads.
|
/// </remarks>
|
[Serializable]
|
[ComVisible(false)]
|
[DebuggerDisplay("Count = {Count}")]
|
// [HostProtection(Synchronization = true, ExternalThreading = true)]
|
public class ConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
|
{
|
[NonSerialized]
|
private volatile Node[] m_buckets;
|
// A singly-linked list for each bucket.
|
[NonSerialized]
|
private object[] m_locks;
|
// A set of locks, each guarding a section of the table.
|
[NonSerialized]
|
private volatile int[] m_countPerLock;
|
// The number of elements guarded by each lock.
|
private IEqualityComparer<TKey> m_comparer;
|
// Key equality comparer
|
|
private KeyValuePair<TKey, TValue>[] m_serializationArray;
|
// Used for custom serialization
|
|
private int m_serializationConcurrencyLevel;
|
// used to save the concurrency level in serialization
|
|
private int m_serializationCapacity;
|
// used to save the capacity in serialization
|
|
// The default concurrency level is DEFAULT_CONCURRENCY_MULTIPLIER * #CPUs. The higher the
|
// DEFAULT_CONCURRENCY_MULTIPLIER, the more concurrent writes can take place without interference
|
// and blocking, but also the more expensive operations that require all locks become (e.g. table
|
// resizing, ToArray, Count, etc). According to brief benchmarks that we ran, 4 seems like a good
|
// compromise.
|
private const int DEFAULT_CONCURRENCY_MULTIPLIER = 4;
|
|
// The default capacity, i.e. the initial # of buckets. When choosing this value, we are making
|
// a trade-off between the size of a very small dictionary, and the number of resizes when
|
// constructing a large dictionary. Also, the capacity should not be divisible by a small prime.
|
private const int DEFAULT_CAPACITY = 31;
|
|
/// <summary>
|
/// Initializes a new instance of the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// class that is empty, has the default concurrency level, has the default initial capacity, and
|
/// uses the default comparer for the key type.
|
/// </summary>
|
public ConcurrentDictionary() : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY)
|
{
|
}
|
|
/// <summary>
|
/// Initializes a new instance of the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// class that is empty, has the specified concurrency level and capacity, and uses the default
|
/// comparer for the key type.
|
/// </summary>
|
/// <param name="concurrencyLevel">The estimated number of threads that will update the
|
/// <see cref="ConcurrentDictionary{TKey,TValue}"/> concurrently.</param>
|
/// <param name="capacity">The initial number of elements that the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// can contain.</param>
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is
|
/// less than 1.</exception>
|
/// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="capacity"/> is less than
|
/// 0.</exception>
|
public ConcurrentDictionary(int concurrencyLevel, int capacity) : this(concurrencyLevel, capacity, EqualityComparer<TKey>.Default)
|
{
|
}
|
|
/// <summary>
|
/// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// class that contains elements copied from the specified <see
|
/// cref="T:System.Collections.IEnumerable{KeyValuePair{TKey,TValue}}"/>, has the default concurrency
|
/// level, has the default initial capacity, and uses the default comparer for the key type.
|
/// </summary>
|
/// <param name="collection">The <see
|
/// cref="T:System.Collections.IEnumerable{KeyValuePair{TKey,TValue}}"/> whose elements are copied to
|
/// the new
|
/// <see cref="ConcurrentDictionary{TKey,TValue}"/>.</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.ArgumentException"><paramref name="collection"/> contains one or more
|
/// duplicate keys.</exception>
|
public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, EqualityComparer<TKey>.Default)
|
{
|
}
|
|
/// <summary>
|
/// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// class that is empty, has the specified concurrency level and capacity, and uses the specified
|
/// <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>.
|
/// </summary>
|
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>
|
/// implementation to use when comparing keys.</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
public ConcurrentDictionary(IEqualityComparer<TKey> comparer) : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, comparer)
|
{
|
}
|
|
/// <summary>
|
/// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// class that contains elements copied from the specified <see
|
/// cref="T:System.Collections.IEnumerable"/>, has the default concurrency level, has the default
|
/// initial capacity, and uses the specified
|
/// <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>.
|
/// </summary>
|
/// <param name="collection">The <see
|
/// cref="T:System.Collections.IEnumerable{KeyValuePair{TKey,TValue}}"/> whose elements are copied to
|
/// the new
|
/// <see cref="ConcurrentDictionary{TKey,TValue}"/>.</param>
|
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>
|
/// implementation to use when comparing keys.</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference
|
/// (Nothing in Visual Basic). -or-
|
/// <paramref name="comparer"/> is a null reference (Nothing in Visual Basic).
|
/// </exception>
|
public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
|
: this(DefaultConcurrencyLevel, collection, comparer)
|
{
|
}
|
|
/// <summary>
|
/// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// class that contains elements copied from the specified <see cref="T:System.Collections.IEnumerable"/>,
|
/// has the specified concurrency level, has the specified initial capacity, and uses the specified
|
/// <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>.
|
/// </summary>
|
/// <param name="concurrencyLevel">The estimated number of threads that will update the
|
/// <see cref="ConcurrentDictionary{TKey,TValue}"/> concurrently.</param>
|
/// <param name="collection">The <see cref="T:System.Collections.IEnumerable{KeyValuePair{TKey,TValue}}"/> whose elements are copied to the new
|
/// <see cref="ConcurrentDictionary{TKey,TValue}"/>.</param>
|
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/> implementation to use
|
/// when comparing keys.</param>
|
/// <exception cref="T:System.ArgumentNullException">
|
/// <paramref name="collection"/> is a null reference (Nothing in Visual Basic).
|
/// -or-
|
/// <paramref name="comparer"/> is a null reference (Nothing in Visual Basic).
|
/// </exception>
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
/// <paramref name="concurrencyLevel"/> is less than 1.
|
/// </exception>
|
/// <exception cref="T:System.ArgumentException"><paramref name="collection"/> contains one or more duplicate keys.</exception>
|
public ConcurrentDictionary(
|
int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
|
: this(concurrencyLevel, DEFAULT_CAPACITY, comparer)
|
{
|
if (collection == null)
|
throw new ArgumentNullException("collection");
|
if (comparer == null)
|
throw new ArgumentNullException("comparer");
|
|
InitializeFromCollection(collection);
|
}
|
|
private void InitializeFromCollection(IEnumerable<KeyValuePair<TKey, TValue>> collection)
|
{
|
TValue dummy;
|
foreach (KeyValuePair<TKey, TValue> pair in collection)
|
{
|
if (pair.Key == null)
|
throw new ArgumentNullException("key");
|
|
if (!TryAddInternal(pair.Key, pair.Value, false, false, out dummy))
|
{
|
throw new ArgumentException(GetResource("ConcurrentDictionary_SourceContainsDuplicateKeys"));
|
}
|
}
|
}
|
|
/// <summary>
|
/// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// class that is empty, has the specified concurrency level, has the specified initial capacity, and
|
/// uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>.
|
/// </summary>
|
/// <param name="concurrencyLevel">The estimated number of threads that will update the
|
/// <see cref="ConcurrentDictionary{TKey,TValue}"/> concurrently.</param>
|
/// <param name="capacity">The initial number of elements that the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// can contain.</param>
|
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>
|
/// implementation to use when comparing keys.</param>
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
/// <paramref name="concurrencyLevel"/> is less than 1. -or-
|
/// <paramref name="capacity"/> is less than 0.
|
/// </exception>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
public ConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
|
{
|
if (concurrencyLevel < 1)
|
{
|
throw new ArgumentOutOfRangeException("concurrencyLevel", GetResource("ConcurrentDictionary_ConcurrencyLevelMustBePositive"));
|
}
|
if (capacity < 0)
|
{
|
throw new ArgumentOutOfRangeException("capacity", GetResource("ConcurrentDictionary_CapacityMustNotBeNegative"));
|
}
|
if (comparer == null)
|
throw new ArgumentNullException("comparer");
|
|
// The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard
|
// any buckets.
|
if (capacity < concurrencyLevel)
|
{
|
capacity = concurrencyLevel;
|
}
|
|
m_locks = new object[concurrencyLevel];
|
for (int i = 0; i < m_locks.Length; i++)
|
{
|
m_locks[i] = new object();
|
}
|
|
m_countPerLock = new int[m_locks.Length];
|
m_buckets = new Node[capacity];
|
m_comparer = comparer;
|
}
|
|
|
/// <summary>
|
/// Attempts to add the specified key and value to the <see cref="ConcurrentDictionary{TKey,
|
/// TValue}"/>.
|
/// </summary>
|
/// <param name="key">The key of the element to add.</param>
|
/// <param name="value">The value of the element to add. The value can be a null reference (Nothing
|
/// in Visual Basic) for reference types.</param>
|
/// <returns>true if the key/value pair was added to the <see cref="ConcurrentDictionary{TKey,
|
/// TValue}"/>
|
/// successfully; otherwise, false.</returns>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.OverflowException">The <see cref="ConcurrentDictionary{TKey, TValue}"/>
|
/// contains too many elements.</exception>
|
public bool TryAdd(TKey key, TValue value)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
TValue dummy;
|
return TryAddInternal(key, value, false, true, out dummy);
|
}
|
|
/// <summary>
|
/// Determines whether the <see cref="ConcurrentDictionary{TKey, TValue}"/> contains the specified
|
/// key.
|
/// </summary>
|
/// <param name="key">The key to locate in the <see cref="ConcurrentDictionary{TKey,
|
/// TValue}"/>.</param>
|
/// <returns>true if the <see cref="ConcurrentDictionary{TKey, TValue}"/> contains an element with
|
/// the specified key; otherwise, false.</returns>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
public bool ContainsKey(TKey key)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
|
TValue throwAwayValue;
|
return TryGetValue(key, out throwAwayValue);
|
}
|
|
/// <summary>
|
/// Attempts to remove and return the the value with the specified key from the
|
/// <see cref="ConcurrentDictionary{TKey, TValue}"/>.
|
/// </summary>
|
/// <param name="key">The key of the element to remove and return.</param>
|
/// <param name="value">When this method returns, <paramref name="value"/> contains the object removed from the
|
/// <see cref="ConcurrentDictionary{TKey,TValue}"/> or the default value of <typeparamref
|
/// name="TValue"/>
|
/// if the operation failed.</param>
|
/// <returns>true if an object was removed successfully; otherwise, false.</returns>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
public bool TryRemove(TKey key, out TValue value)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
|
return TryRemoveInternal(key, out value, false, default(TValue));
|
}
|
|
/// <summary>
|
/// Removes the specified key from the dictionary if it exists and returns its associated value.
|
/// If matchValue flag is set, the key will be removed only if is associated with a particular
|
/// value.
|
/// </summary>
|
/// <param name="key">The key to search for and remove if it exists.</param>
|
/// <param name="value">The variable into which the removed value, if found, is stored.</param>
|
/// <param name="matchValue">Whether removal of the key is conditional on its value.</param>
|
/// <param name="oldValue">The conditional value to compare against if <paramref name="matchValue"/> is true</param>
|
/// <returns></returns>
|
private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue oldValue)
|
{
|
while (true)
|
{
|
Node[] buckets = m_buckets;
|
|
int bucketNo, lockNo;
|
GetBucketAndLockNo(m_comparer.GetHashCode(key), out bucketNo, out lockNo, buckets.Length);
|
|
lock (m_locks[lockNo])
|
{
|
// If the table just got resized, we may not be holding the right lock, and must retry.
|
// This should be a rare occurence.
|
if (buckets != m_buckets)
|
{
|
continue;
|
}
|
|
Node prev = null;
|
for (Node curr = m_buckets[bucketNo]; curr != null; curr = curr.m_next)
|
{
|
Assert((prev == null && curr == m_buckets[bucketNo]) || prev.m_next == curr);
|
|
if (m_comparer.Equals(curr.m_key, key))
|
{
|
if (matchValue)
|
{
|
bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue, curr.m_value);
|
if (!valuesMatch)
|
{
|
value = default(TValue);
|
return false;
|
}
|
}
|
|
if (prev == null)
|
{
|
m_buckets[bucketNo] = curr.m_next;
|
}
|
else
|
{
|
prev.m_next = curr.m_next;
|
}
|
|
value = curr.m_value;
|
m_countPerLock[lockNo]--;
|
return true;
|
}
|
prev = curr;
|
}
|
}
|
|
value = default(TValue);
|
return false;
|
}
|
}
|
|
/// <summary>
|
/// Attempts to get the value associated with the specified key from the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>.
|
/// </summary>
|
/// <param name="key">The key of the value to get.</param>
|
/// <param name="value">When this method returns, <paramref name="value"/> contains the object from
|
/// the
|
/// <see cref="ConcurrentDictionary{TKey,TValue}"/> with the spedified key or the default value of
|
/// <typeparamref name="TValue"/>, if the operation failed.</param>
|
/// <returns>true if the key was found in the <see cref="ConcurrentDictionary{TKey,TValue}"/>;
|
/// otherwise, false.</returns>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
public bool TryGetValue(TKey key, out TValue value)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
|
int bucketNo, lockNoUnused;
|
|
// We must capture the m_buckets field in a local variable. It is set to a new table on each table resize.
|
Node[] buckets = m_buckets;
|
GetBucketAndLockNo(m_comparer.GetHashCode(key), out bucketNo, out lockNoUnused, buckets.Length);
|
|
// We can get away w/out a lock here.
|
Node n = buckets[bucketNo];
|
|
// The memory barrier ensures that the load of the fields of 'n' doesn’t move before the load from buckets[i].
|
Thread.MemoryBarrier();
|
while (n != null)
|
{
|
if (m_comparer.Equals(n.m_key, key))
|
{
|
value = n.m_value;
|
return true;
|
}
|
n = n.m_next;
|
}
|
|
value = default(TValue);
|
return false;
|
}
|
|
/// <summary>
|
/// Compares the existing value for the specified key with a specified value, and if they’re equal,
|
/// updates the key with a third value.
|
/// </summary>
|
/// <param name="key">The key whose value is compared with <paramref name="comparisonValue"/> and
|
/// possibly replaced.</param>
|
/// <param name="newValue">The value that replaces the value of the element with <paramref
|
/// name="key"/> if the comparison results in equality.</param>
|
/// <param name="comparisonValue">The value that is compared to the value of the element with
|
/// <paramref name="key"/>.</param>
|
/// <returns>true if the value with <paramref name="key"/> was equal to <paramref
|
/// name="comparisonValue"/> and replaced with <paramref name="newValue"/>; otherwise,
|
/// false.</returns>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null
|
/// reference.</exception>
|
public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
|
int hashcode = m_comparer.GetHashCode(key);
|
IEqualityComparer<TValue> valueComparer = EqualityComparer<TValue>.Default;
|
|
while (true)
|
{
|
int bucketNo;
|
int lockNo;
|
|
Node[] buckets = m_buckets;
|
GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, buckets.Length);
|
|
lock (m_locks[lockNo])
|
{
|
// If the table just got resized, we may not be holding the right lock, and must retry.
|
// This should be a rare occurence.
|
if (buckets != m_buckets)
|
{
|
continue;
|
}
|
|
// Try to find this key in the bucket
|
Node prev = null;
|
for (Node node = buckets[bucketNo]; node != null; node = node.m_next)
|
{
|
Assert((prev == null && node == m_buckets[bucketNo]) || prev.m_next == node);
|
if (m_comparer.Equals(node.m_key, key))
|
{
|
if (valueComparer.Equals(node.m_value, comparisonValue))
|
{
|
// Replace the old node with a new node. Unfortunately, we cannot simply
|
// change node.m_value in place. We don't know the size of TValue, so
|
// its writes may not be atomic.
|
Node newNode = new Node(node.m_key, newValue, hashcode, node.m_next);
|
|
if (prev == null)
|
{
|
buckets[bucketNo] = newNode;
|
}
|
else
|
{
|
prev.m_next = newNode;
|
}
|
|
return true;
|
}
|
|
return false;
|
}
|
|
prev = node;
|
}
|
|
//didn't find the key
|
return false;
|
}
|
}
|
}
|
|
/// <summary>
|
/// Removes all keys and values from the <see cref="ConcurrentDictionary{TKey,TValue}"/>.
|
/// </summary>
|
public void Clear()
|
{
|
int locksAcquired = 0;
|
try
|
{
|
AcquireAllLocks(ref locksAcquired);
|
|
m_buckets = new Node[DEFAULT_CAPACITY];
|
Array.Clear(m_countPerLock, 0, m_countPerLock.Length);
|
}
|
finally
|
{
|
ReleaseLocks(0, locksAcquired);
|
}
|
}
|
|
/// <summary>
|
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection"/> to an array of
|
/// type <see cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/>, starting at the
|
/// specified array index.
|
/// </summary>
|
/// <param name="array">The one-dimensional array of type <see
|
/// cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/>
|
/// that is the destination of the <see
|
/// cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/> elements copied from the <see
|
/// cref="T:System.Collections.ICollection"/>. The array must have zero-based indexing.</param>
|
/// <param name="index">The zero-based index in <paramref name="array"/> at which copying
|
/// begins.</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="array"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than
|
/// 0.</exception>
|
/// <exception cref="T:System.ArgumentException"><paramref name="index"/> is equal to or greater than
|
/// the length of the <paramref name="array"/>. -or- The number of elements in the source <see
|
/// cref="T:System.Collections.ICollection"/>
|
/// is greater than the available space from <paramref name="index"/> to the end of the destination
|
/// <paramref name="array"/>.</exception>
|
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
|
{
|
if (array == null)
|
throw new ArgumentNullException("array");
|
if (index < 0)
|
throw new ArgumentOutOfRangeException("index", GetResource("ConcurrentDictionary_IndexIsNegative"));
|
|
int locksAcquired = 0;
|
try
|
{
|
AcquireAllLocks(ref locksAcquired);
|
|
int count = 0;
|
|
for (int i = 0; i < m_locks.Length; i++)
|
{
|
count += m_countPerLock[i];
|
}
|
|
if (array.Length - count < index || count < 0)
|
{ //"count" itself or "count + index" can overflow
|
throw new ArgumentException(GetResource("ConcurrentDictionary_ArrayNotLargeEnough"));
|
}
|
|
CopyToPairs(array, index);
|
}
|
finally
|
{
|
ReleaseLocks(0, locksAcquired);
|
}
|
}
|
|
/// <summary>
|
/// Copies the key and value pairs stored in the <see cref="ConcurrentDictionary{TKey,TValue}"/> to a
|
/// new array.
|
/// </summary>
|
/// <returns>A new array containing a snapshot of key and value pairs copied from the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>.</returns>
|
public KeyValuePair<TKey, TValue>[] ToArray()
|
{
|
int locksAcquired = 0;
|
try
|
{
|
AcquireAllLocks(ref locksAcquired);
|
int count = 0;
|
checked
|
{
|
for (int i = 0; i < m_locks.Length; i++)
|
{
|
count += m_countPerLock[i];
|
}
|
}
|
|
KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[count];
|
|
CopyToPairs(array, 0);
|
return array;
|
}
|
finally
|
{
|
ReleaseLocks(0, locksAcquired);
|
}
|
}
|
|
/// <summary>
|
/// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo.
|
///
|
/// Important: the caller must hold all locks in m_locks before calling CopyToPairs.
|
/// </summary>
|
private void CopyToPairs(KeyValuePair<TKey, TValue>[] array, int index)
|
{
|
Node[] buckets = m_buckets;
|
for (int i = 0; i < buckets.Length; i++)
|
{
|
for (Node current = buckets[i]; current != null; current = current.m_next)
|
{
|
array[index] = new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
|
index++; //this should never flow, CopyToPairs is only called when there's no overflow risk
|
}
|
}
|
}
|
|
/// <summary>
|
/// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo.
|
///
|
/// Important: the caller must hold all locks in m_locks before calling CopyToEntries.
|
/// </summary>
|
private void CopyToEntries(DictionaryEntry[] array, int index)
|
{
|
Node[] buckets = m_buckets;
|
for (int i = 0; i < buckets.Length; i++)
|
{
|
for (Node current = buckets[i]; current != null; current = current.m_next)
|
{
|
array[index] = new DictionaryEntry(current.m_key, current.m_value);
|
index++; //this should never flow, CopyToEntries is only called when there's no overflow risk
|
}
|
}
|
}
|
|
/// <summary>
|
/// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo.
|
///
|
/// Important: the caller must hold all locks in m_locks before calling CopyToObjects.
|
/// </summary>
|
private void CopyToObjects(object[] array, int index)
|
{
|
Node[] buckets = m_buckets;
|
for (int i = 0; i < buckets.Length; i++)
|
{
|
for (Node current = buckets[i]; current != null; current = current.m_next)
|
{
|
array[index] = new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
|
index++; //this should never flow, CopyToObjects is only called when there's no overflow risk
|
}
|
}
|
}
|
|
/// <summary>Returns an enumerator that iterates through the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>.</summary>
|
/// <returns>An enumerator for the <see cref="ConcurrentDictionary{TKey,TValue}"/>.</returns>
|
/// <remarks>
|
/// The enumerator returned from the dictionary is safe to use concurrently with
|
/// reads and writes to the dictionary, however it does not represent a moment-in-time snapshot
|
/// of the dictionary. The contents exposed through the enumerator may contain modifications
|
/// made to the dictionary after <see cref="GetEnumerator"/> was called.
|
/// </remarks>
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
{
|
Node[] buckets = m_buckets;
|
|
for (int i = 0; i < buckets.Length; i++)
|
{
|
Node current = buckets[i];
|
|
// The memory barrier ensures that the load of the fields of 'current' doesn’t move before the load from buckets[i].
|
Thread.MemoryBarrier();
|
while (current != null)
|
{
|
yield return new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
|
current = current.m_next;
|
}
|
}
|
}
|
|
/// <summary>
|
/// Shared internal implementation for inserts and updates.
|
/// If key exists, we always return false; and if updateIfExists == true we force update with value;
|
/// If key doesn't exist, we always add value and return true;
|
/// </summary>
|
private bool TryAddInternal(TKey key, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue)
|
{
|
int hashcode = m_comparer.GetHashCode(key);
|
|
while (true)
|
{
|
int bucketNo, lockNo;
|
|
Node[] buckets = m_buckets;
|
GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, buckets.Length);
|
|
bool resizeDesired = false;
|
bool lockTaken = false;
|
try
|
{
|
if (acquireLock)
|
{
|
Monitor.Enter(m_locks[lockNo]);
|
lockTaken = true;
|
}
|
|
// If the table just got resized, we may not be holding the right lock, and must retry.
|
// This should be a rare occurence.
|
if (buckets != m_buckets)
|
{
|
continue;
|
}
|
|
// Try to find this key in the bucket
|
Node prev = null;
|
for (Node node = buckets[bucketNo]; node != null; node = node.m_next)
|
{
|
Assert((prev == null && node == m_buckets[bucketNo]) || prev.m_next == node);
|
if (m_comparer.Equals(node.m_key, key))
|
{
|
// The key was found in the dictionary. If updates are allowed, update the value for that key.
|
// We need to create a new node for the update, in order to support TValue types that cannot
|
// be written atomically, since lock-free reads may be happening concurrently.
|
if (updateIfExists)
|
{
|
Node newNode = new Node(node.m_key, value, hashcode, node.m_next);
|
if (prev == null)
|
{
|
buckets[bucketNo] = newNode;
|
}
|
else
|
{
|
prev.m_next = newNode;
|
}
|
resultingValue = value;
|
}
|
else
|
{
|
resultingValue = node.m_value;
|
}
|
return false;
|
}
|
prev = node;
|
}
|
|
// The key was not found in the bucket. Insert the key-value pair.
|
buckets[bucketNo] = new Node(key, value, hashcode, buckets[bucketNo]);
|
checked
|
{
|
m_countPerLock[lockNo]++;
|
}
|
|
//
|
// If this lock has element / bucket ratio greater than 1, resize the entire table.
|
// Note: the formula is chosen to avoid overflow, but has a small inaccuracy due to
|
// rounding.
|
//
|
if (m_countPerLock[lockNo] > buckets.Length / m_locks.Length)
|
{
|
resizeDesired = true;
|
}
|
}
|
finally
|
{
|
if (lockTaken)
|
Monitor.Exit(m_locks[lockNo]);
|
}
|
|
//
|
// The fact that we got here means that we just performed an insertion. If necessary, we will grow the table.
|
//
|
// Concurrency notes:
|
// - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks.
|
// - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0
|
// and then verify that the table we passed to it as the argument is still the current table.
|
//
|
if (resizeDesired)
|
{
|
GrowTable(buckets);
|
}
|
|
resultingValue = value;
|
return true;
|
}
|
}
|
|
/// <summary>
|
/// Gets or sets the value associated with the specified key.
|
/// </summary>
|
/// <param name="key">The key of the value to get or set.</param>
|
/// <value>The value associated with the specified key. If the specified key is not found, a get
|
/// operation throws a
|
/// <see cref="T:Sytem.Collections.Generic.KeyNotFoundException"/>, and a set operation creates a new
|
/// element with the specified key.</value>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.Collections.Generic.KeyNotFoundException">The property is retrieved and
|
/// <paramref name="key"/>
|
/// does not exist in the collection.</exception>
|
public TValue this[TKey key]
|
{
|
get
|
{
|
TValue value;
|
if (!TryGetValue(key, out value))
|
{
|
throw new KeyNotFoundException();
|
}
|
return value;
|
}
|
set
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
TValue dummy;
|
TryAddInternal(key, value, true, true, out dummy);
|
}
|
}
|
|
/// <summary>
|
/// Gets the number of key/value pairs contained in the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>.
|
/// </summary>
|
/// <exception cref="T:System.OverflowException">The dictionary contains too many
|
/// elements.</exception>
|
/// <value>The number of key/value paris contained in the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>.</value>
|
/// <remarks>Count has snapshot semantics and represents the number of items in the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// at the moment when Count was accessed.</remarks>
|
public int Count
|
{
|
get
|
{
|
int count = 0;
|
|
int acquiredLocks = 0;
|
try
|
{
|
// Acquire all locks
|
AcquireAllLocks(ref acquiredLocks);
|
|
// Compute the count, we allow overflow
|
for (int i = 0; i < m_countPerLock.Length; i++)
|
{
|
count += m_countPerLock[i];
|
}
|
|
}
|
finally
|
{
|
// Release locks that have been acquired earlier
|
ReleaseLocks(0, acquiredLocks);
|
}
|
|
return count;
|
}
|
}
|
|
/// <summary>
|
/// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// if the key does not already exist.
|
/// </summary>
|
/// <param name="key">The key of the element to add.</param>
|
/// <param name="valueFactory">The function used to generate a value for the key</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="valueFactory"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.OverflowException">The dictionary contains too many
|
/// elements.</exception>
|
/// <returns>The value for the key. This will be either the existing value for the key if the
|
/// key is already in the dictionary, or the new value for the key as returned by valueFactory
|
/// if the key was not in the dictionary.</returns>
|
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
if (valueFactory == null)
|
throw new ArgumentNullException("valueFactory");
|
|
TValue resultingValue;
|
if (TryGetValue(key, out resultingValue))
|
{
|
return resultingValue;
|
}
|
TryAddInternal(key, valueFactory(key), false, true, out resultingValue);
|
return resultingValue;
|
}
|
|
/// <summary>
|
/// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey,TValue}"/>
|
/// if the key does not already exist.
|
/// </summary>
|
/// <param name="key">The key of the element to add.</param>
|
/// <param name="value">the value to be added, if the key does not already exist</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.OverflowException">The dictionary contains too many
|
/// elements.</exception>
|
/// <returns>The value for the key. This will be either the existing value for the key if the
|
/// key is already in the dictionary, or the new value if the key was not in the dictionary.</returns>
|
public TValue GetOrAdd(TKey key, TValue value)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
|
TValue resultingValue;
|
TryAddInternal(key, value, false, true, out resultingValue);
|
return resultingValue;
|
}
|
|
/// <summary>
|
/// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey,TValue}"/> if the key does not already
|
/// exist, or updates a key/value pair in the <see cref="ConcurrentDictionary{TKey,TValue}"/> if the key
|
/// already exists.
|
/// </summary>
|
/// <param name="key">The key to be added or whose value should be updated</param>
|
/// <param name="addValueFactory">The function used to generate a value for an absent key</param>
|
/// <param name="updateValueFactory">The function used to generate a new value for an existing key
|
/// based on the key's existing value</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="addValueFactory"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.OverflowException">The dictionary contains too many
|
/// elements.</exception>
|
/// <returns>The new value for the key. This will be either be the result of addValueFactory (if the key was
|
/// absent) or the result of updateValueFactory (if the key was present).</returns>
|
public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
if (addValueFactory == null)
|
throw new ArgumentNullException("addValueFactory");
|
if (updateValueFactory == null)
|
throw new ArgumentNullException("updateValueFactory");
|
|
TValue newValue, resultingValue;
|
while (true)
|
{
|
TValue oldValue;
|
if (TryGetValue(key, out oldValue))
|
{ //key exists, try to update
|
newValue = updateValueFactory(key, oldValue);
|
if (TryUpdate(key, newValue, oldValue))
|
{
|
return newValue;
|
}
|
}
|
else
|
{ //try add
|
newValue = addValueFactory(key);
|
if (TryAddInternal(key, newValue, false, true, out resultingValue))
|
{
|
return resultingValue;
|
}
|
}
|
}
|
}
|
|
/// <summary>
|
/// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey,TValue}"/> if the key does not already
|
/// exist, or updates a key/value pair in the <see cref="ConcurrentDictionary{TKey,TValue}"/> if the key
|
/// already exists.
|
/// </summary>
|
/// <param name="key">The key to be added or whose value should be updated</param>
|
/// <param name="addValue">The value to be added for an absent key</param>
|
/// <param name="updateValueFactory">The function used to generate a new value for an existing key based on
|
/// the key's existing value</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.OverflowException">The dictionary contains too many
|
/// elements.</exception>
|
/// <returns>The new value for the key. This will be either be the result of addValueFactory (if the key was
|
/// absent) or the result of updateValueFactory (if the key was present).</returns>
|
public TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
if (updateValueFactory == null)
|
throw new ArgumentNullException("updateValueFactory");
|
TValue newValue, resultingValue;
|
while (true)
|
{
|
TValue oldValue;
|
if (TryGetValue(key, out oldValue))
|
{ //key exists, try to update
|
newValue = updateValueFactory(key, oldValue);
|
if (TryUpdate(key, newValue, oldValue))
|
{
|
return newValue;
|
}
|
}
|
else
|
{ //try add
|
if (TryAddInternal(key, addValue, false, true, out resultingValue))
|
{
|
return resultingValue;
|
}
|
}
|
}
|
}
|
|
|
|
/// <summary>
|
/// Gets a value that indicates whether the <see cref="ConcurrentDictionary{TKey,TValue}"/> is empty.
|
/// </summary>
|
/// <value>true if the <see cref="ConcurrentDictionary{TKey,TValue}"/> is empty; otherwise,
|
/// false.</value>
|
public bool IsEmpty
|
{
|
get
|
{
|
int acquiredLocks = 0;
|
try
|
{
|
// Acquire all locks
|
AcquireAllLocks(ref acquiredLocks);
|
|
for (int i = 0; i < m_countPerLock.Length; i++)
|
{
|
if (m_countPerLock[i] != 0)
|
{
|
return false;
|
}
|
}
|
}
|
finally
|
{
|
// Release locks that have been acquired earlier
|
ReleaseLocks(0, acquiredLocks);
|
}
|
|
return true;
|
}
|
}
|
|
#region IDictionary<TKey,TValue> members
|
|
/// <summary>
|
/// Adds the specified key and value to the <see
|
/// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.
|
/// </summary>
|
/// <param name="key">The object to use as the key of the element to add.</param>
|
/// <param name="value">The object to use as the value of the element to add.</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.OverflowException">The dictionary contains too many
|
/// elements.</exception>
|
/// <exception cref="T:System.ArgumentException">
|
/// An element with the same key already exists in the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>.</exception>
|
void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
|
{
|
if (!TryAdd(key, value))
|
{
|
throw new ArgumentException(GetResource("ConcurrentDictionary_KeyAlreadyExisted"));
|
}
|
}
|
|
/// <summary>
|
/// Removes the element with the specified key from the <see
|
/// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.
|
/// </summary>
|
/// <param name="key">The key of the element to remove.</param>
|
/// <returns>true if the element is successfully remove; otherwise false. This method also returns
|
/// false if
|
/// <paramref name="key"/> was not found in the original <see
|
/// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.
|
/// </returns>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
bool IDictionary<TKey, TValue>.Remove(TKey key)
|
{
|
TValue throwAwayValue;
|
return TryRemove(key, out throwAwayValue);
|
}
|
|
/// <summary>
|
/// Gets a collection containing the keys in the <see
|
/// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.
|
/// </summary>
|
/// <value>An <see cref="T:System.Collections.Generic.ICollection{TKey}"/> containing the keys in the
|
/// <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.</value>
|
public ICollection<TKey> Keys
|
{
|
get { return GetKeys(); }
|
}
|
|
/// <summary>
|
/// Gets a collection containing the values in the <see
|
/// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.
|
/// </summary>
|
/// <value>An <see cref="T:System.Collections.Generic.ICollection{TValue}"/> containing the values in
|
/// the
|
/// <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.</value>
|
public ICollection<TValue> Values
|
{
|
get { return GetValues(); }
|
}
|
|
#endregion
|
|
#region ICollection<KeyValuePair<TKey,TValue>> Members
|
|
/// <summary>
|
/// Adds the specified value to the <see cref="T:System.Collections.Generic.ICollection{TValue}"/>
|
/// with the specified key.
|
/// </summary>
|
/// <param name="keyValuePair">The <see cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/>
|
/// structure representing the key and value to add to the <see
|
/// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.</param>
|
/// <exception cref="T:System.ArgumentNullException">The <paramref name="keyValuePair"/> of <paramref
|
/// name="keyValuePair"/> is null.</exception>
|
/// <exception cref="T:System.OverflowException">The <see
|
/// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>
|
/// contains too many elements.</exception>
|
/// <exception cref="T:System.ArgumentException">An element with the same key already exists in the
|
/// <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/></exception>
|
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> keyValuePair)
|
{
|
((IDictionary<TKey, TValue>)this).Add(keyValuePair.Key, keyValuePair.Value);
|
}
|
|
/// <summary>
|
/// Determines whether the <see cref="T:System.Collections.Generic.ICollection{TKey,TValue}"/>
|
/// contains a specific key and value.
|
/// </summary>
|
/// <param name="keyValuePair">The <see cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/>
|
/// structure to locate in the <see
|
/// cref="T:System.Collections.Generic.ICollection{TValue}"/>.</param>
|
/// <returns>true if the <paramref name="keyValuePair"/> is found in the <see
|
/// cref="T:System.Collections.Generic.ICollection{TKey,TValue}"/>; otherwise, false.</returns>
|
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair)
|
{
|
TValue value;
|
if (!TryGetValue(keyValuePair.Key, out value))
|
{
|
return false;
|
}
|
return EqualityComparer<TValue>.Default.Equals(value, keyValuePair.Value);
|
}
|
|
/// <summary>
|
/// Gets a value indicating whether the dictionary is read-only.
|
/// </summary>
|
/// <value>true if the <see cref="T:System.Collections.Generic.ICollection{TKey,TValue}"/> is
|
/// read-only; otherwise, false. For <see
|
/// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>, this property always returns
|
/// false.</value>
|
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
|
{
|
get { return false; }
|
}
|
|
/// <summary>
|
/// Removes a key and value from the dictionary.
|
/// </summary>
|
/// <param name="keyValuePair">The <see
|
/// cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/>
|
/// structure representing the key and value to remove from the <see
|
/// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.</param>
|
/// <returns>true if the key and value represented by <paramref name="keyValuePair"/> is successfully
|
/// found and removed; otherwise, false.</returns>
|
/// <exception cref="T:System.ArgumentNullException">The Key property of <paramref
|
/// name="keyValuePair"/> is a null reference (Nothing in Visual Basic).</exception>
|
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair)
|
{
|
if (keyValuePair.Key == null)
|
throw new ArgumentNullException(GetResource("ConcurrentDictionary_ItemKeyIsNull"));
|
|
TValue throwAwayValue;
|
return TryRemoveInternal(keyValuePair.Key, out throwAwayValue, true, keyValuePair.Value);
|
}
|
|
#endregion
|
|
#region IEnumerable Members
|
|
/// <summary>Returns an enumerator that iterates through the <see
|
/// cref="ConcurrentDictionary{TKey,TValue}"/>.</summary>
|
/// <returns>An enumerator for the <see cref="ConcurrentDictionary{TKey,TValue}"/>.</returns>
|
/// <remarks>
|
/// The enumerator returned from the dictionary is safe to use concurrently with
|
/// reads and writes to the dictionary, however it does not represent a moment-in-time snapshot
|
/// of the dictionary. The contents exposed through the enumerator may contain modifications
|
/// made to the dictionary after <see cref="GetEnumerator"/> was called.
|
/// </remarks>
|
IEnumerator IEnumerable.GetEnumerator()
|
{
|
return ((ConcurrentDictionary<TKey, TValue>)this).GetEnumerator();
|
}
|
|
#endregion
|
|
#region IDictionary Members
|
|
/// <summary>
|
/// Adds the specified key and value to the dictionary.
|
/// </summary>
|
/// <param name="key">The object to use as the key.</param>
|
/// <param name="value">The object to use as the value.</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.OverflowException">The dictionary contains too many
|
/// elements.</exception>
|
/// <exception cref="T:System.ArgumentException">
|
/// <paramref name="key"/> is of a type that is not assignable to the key type <typeparamref
|
/// name="TKey"/> of the <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>. -or-
|
/// <paramref name="value"/> is of a type that is not assignable to <typeparamref name="TValue"/>,
|
/// the type of values in the <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.
|
/// -or- A value with the same key already exists in the <see
|
/// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.
|
/// </exception>
|
void IDictionary.Add(object key, object value)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
if (!(key is TKey))
|
throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfKeyIncorrect"));
|
|
TValue typedValue;
|
try
|
{
|
typedValue = (TValue)value;
|
}
|
catch (InvalidCastException)
|
{
|
throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfValueIncorrect"));
|
}
|
|
((IDictionary<TKey, TValue>)this).Add((TKey)key, typedValue);
|
}
|
|
/// <summary>
|
/// Gets whether the <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> contains an
|
/// element with the specified key.
|
/// </summary>
|
/// <param name="key">The key to locate in the <see
|
/// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.</param>
|
/// <returns>true if the <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> contains
|
/// an element with the specified key; otherwise, false.</returns>
|
/// <exception cref="T:System.ArgumentNullException"> <paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
bool IDictionary.Contains(object key)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
|
return (key is TKey) && ((ConcurrentDictionary<TKey, TValue>)this).ContainsKey((TKey)key);
|
}
|
|
/// <summary>Provides an <see cref="T:System.Collections.Generics.IDictionaryEnumerator"/> for the
|
/// <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.</summary>
|
/// <returns>An <see cref="T:System.Collections.Generics.IDictionaryEnumerator"/> for the <see
|
/// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.</returns>
|
IDictionaryEnumerator IDictionary.GetEnumerator()
|
{
|
return new DictionaryEnumerator(this);
|
}
|
|
/// <summary>
|
/// Gets a value indicating whether the <see
|
/// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> has a fixed size.
|
/// </summary>
|
/// <value>true if the <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> has a
|
/// fixed size; otherwise, false. For <see
|
/// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/>, this property always
|
/// returns false.</value>
|
bool IDictionary.IsFixedSize
|
{
|
get { return false; }
|
}
|
|
/// <summary>
|
/// Gets a value indicating whether the <see
|
/// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> is read-only.
|
/// </summary>
|
/// <value>true if the <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> is
|
/// read-only; otherwise, false. For <see
|
/// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/>, this property always
|
/// returns false.</value>
|
bool IDictionary.IsReadOnly
|
{
|
get { return false; }
|
}
|
|
/// <summary>
|
/// Gets an <see cref="T:System.Collections.ICollection"/> containing the keys of the <see
|
/// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.
|
/// </summary>
|
/// <value>An <see cref="T:System.Collections.ICollection"/> containing the keys of the <see
|
/// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.</value>
|
ICollection IDictionary.Keys
|
{
|
get { return GetKeys(); }
|
}
|
|
/// <summary>
|
/// Removes the element with the specified key from the <see
|
/// cref="T:System.Collections.IDictionary"/>.
|
/// </summary>
|
/// <param name="key">The key of the element to remove.</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
void IDictionary.Remove(object key)
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
|
TValue throwAwayValue;
|
if (key is TKey)
|
{
|
this.TryRemove((TKey)key, out throwAwayValue);
|
}
|
}
|
|
/// <summary>
|
/// Gets an <see cref="T:System.Collections.ICollection"/> containing the values in the <see
|
/// cref="T:System.Collections.IDictionary"/>.
|
/// </summary>
|
/// <value>An <see cref="T:System.Collections.ICollection"/> containing the values in the <see
|
/// cref="T:System.Collections.IDictionary"/>.</value>
|
ICollection IDictionary.Values
|
{
|
get { return GetValues(); }
|
}
|
|
/// <summary>
|
/// Gets or sets the value associated with the specified key.
|
/// </summary>
|
/// <param name="key">The key of the value to get or set.</param>
|
/// <value>The value associated with the specified key, or a null reference (Nothing in Visual Basic)
|
/// if <paramref name="key"/> is not in the dictionary or <paramref name="key"/> is of a type that is
|
/// not assignable to the key type <typeparamref name="TKey"/> of the <see
|
/// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/>.</value>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.ArgumentException">
|
/// A value is being assigned, and <paramref name="key"/> is of a type that is not assignable to the
|
/// key type <typeparamref name="TKey"/> of the <see
|
/// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/>. -or- A value is being
|
/// assigned, and <paramref name="key"/> is of a type that is not assignable to the value type
|
/// <typeparamref name="TValue"/> of the <see
|
/// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/>
|
/// </exception>
|
object IDictionary.this[object key]
|
{
|
get
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
|
TValue value;
|
if (key is TKey && this.TryGetValue((TKey)key, out value))
|
{
|
return value;
|
}
|
|
return null;
|
}
|
set
|
{
|
if (key == null)
|
throw new ArgumentNullException("key");
|
|
if (!(key is TKey))
|
throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfKeyIncorrect"));
|
if (!(value is TValue))
|
throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfValueIncorrect"));
|
|
((ConcurrentDictionary<TKey, TValue>)this)[(TKey)key] = (TValue)value;
|
}
|
}
|
|
#endregion
|
|
#region ICollection Members
|
|
/// <summary>
|
/// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an array, starting
|
/// at the specified array index.
|
/// </summary>
|
/// <param name="array">The one-dimensional array that is the destination of the elements copied from
|
/// the <see cref="T:System.Collections.ICollection"/>. The array must have zero-based
|
/// indexing.</param>
|
/// <param name="index">The zero-based index in <paramref name="array"/> at which copying
|
/// begins.</param>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="array"/> is a null reference
|
/// (Nothing in Visual Basic).</exception>
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than
|
/// 0.</exception>
|
/// <exception cref="T:System.ArgumentException"><paramref name="index"/> is equal to or greater than
|
/// the length of the <paramref name="array"/>. -or- The number of elements in the source <see
|
/// cref="T:System.Collections.ICollection"/>
|
/// is greater than the available space from <paramref name="index"/> to the end of the destination
|
/// <paramref name="array"/>.</exception>
|
void ICollection.CopyTo(Array array, int index)
|
{
|
if (array == null)
|
throw new ArgumentNullException("array");
|
if (index < 0)
|
throw new ArgumentOutOfRangeException("index", GetResource("ConcurrentDictionary_IndexIsNegative"));
|
|
int locksAcquired = 0;
|
try
|
{
|
AcquireAllLocks(ref locksAcquired);
|
|
int count = 0;
|
|
for (int i = 0; i < m_locks.Length; i++)
|
{
|
count += m_countPerLock[i];
|
}
|
|
if (array.Length - count < index || count < 0)
|
{ //"count" itself or "count + index" can overflow
|
throw new ArgumentException(GetResource("ConcurrentDictionary_ArrayNotLargeEnough"));
|
}
|
|
// To be consistent with the behavior of ICollection.CopyTo() in Dictionary<TKey,TValue>,
|
// we recognize three types of target arrays:
|
// - an array of KeyValuePair<TKey, TValue> structs
|
// - an array of DictionaryEntry structs
|
// - an array of objects
|
|
KeyValuePair<TKey, TValue>[] pairs = array as KeyValuePair<TKey, TValue>[];
|
if (pairs != null)
|
{
|
CopyToPairs(pairs, index);
|
return;
|
}
|
|
DictionaryEntry[] entries = array as DictionaryEntry[];
|
if (entries != null)
|
{
|
CopyToEntries(entries, index);
|
return;
|
}
|
|
object[] objects = array as object[];
|
if (objects != null)
|
{
|
CopyToObjects(objects, index);
|
return;
|
}
|
|
throw new ArgumentException(GetResource("ConcurrentDictionary_ArrayIncorrectType"), "array");
|
}
|
finally
|
{
|
ReleaseLocks(0, locksAcquired);
|
}
|
}
|
|
/// <summary>
|
/// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"/> is
|
/// synchronized with the SyncRoot.
|
/// </summary>
|
/// <value>true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized
|
/// (thread safe); otherwise, false. For <see
|
/// cref="T:System.Collections.Concurrent.ConcurrentDictionary{TKey,TValue}"/>, this property always
|
/// returns false.</value>
|
bool ICollection.IsSynchronized
|
{
|
get { return false; }
|
}
|
|
/// <summary>
|
/// Gets an object that can be used to synchronize access to the <see
|
/// cref="T:System.Collections.ICollection"/>. This property is not supported.
|
/// </summary>
|
/// <exception cref="T:System.NotSupportedException">The SyncRoot property is not supported.</exception>
|
object ICollection.SyncRoot
|
{
|
get
|
{
|
throw new NotSupportedException();
|
}
|
}
|
|
#endregion
|
|
/// <summary>
|
/// Replaces the internal table with a larger one. To prevent multiple threads from resizing the
|
/// table as a result of ----s, the table of buckets that was deemed too small is passed in as
|
/// an argument to GrowTable(). GrowTable() obtains a lock, and then checks whether the bucket
|
/// table has been replaced in the meantime or not.
|
/// </summary>
|
/// <param name="buckets">Reference to the bucket table that was deemed too small.</param>
|
private void GrowTable(Node[] buckets)
|
{
|
int locksAcquired = 0;
|
try
|
{
|
// The thread that first obtains m_locks[0] will be the one doing the resize operation
|
AcquireLocks(0, 1, ref locksAcquired);
|
|
// Make sure nobody resized the table while we were waiting for lock 0:
|
if (buckets != m_buckets)
|
{
|
// We assume that since the table reference is different, it was already resized. If we ever
|
// decide to do table shrinking, or replace the table for other reasons, we will have to revisit
|
// this logic.
|
return;
|
}
|
|
// Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by
|
// 2,3,5 or 7. We can consider a different table-sizing policy in the future.
|
int newLength;
|
try
|
{
|
checked
|
{
|
// Double the size of the buckets table and add one, so that we have an odd integer.
|
newLength = buckets.Length * 2 + 1;
|
|
// Now, we only need to check odd integers, and find the first that is not divisible
|
// by 3, 5 or 7.
|
while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0)
|
{
|
newLength += 2;
|
}
|
|
Assert(newLength % 2 != 0);
|
}
|
}
|
catch (OverflowException)
|
{
|
// If we were to resize the table, its new size will not fit into a 32-bit signed int. Just return.
|
return;
|
}
|
|
Node[] newBuckets = new Node[newLength];
|
int[] newCountPerLock = new int[m_locks.Length];
|
|
// Now acquire all other locks for the table
|
AcquireLocks(1, m_locks.Length, ref locksAcquired);
|
|
// Copy all data into a new table, creating new nodes for all elements
|
for (int i = 0; i < buckets.Length; i++)
|
{
|
Node current = buckets[i];
|
while (current != null)
|
{
|
Node next = current.m_next;
|
int newBucketNo, newLockNo;
|
GetBucketAndLockNo(current.m_hashcode, out newBucketNo, out newLockNo, newBuckets.Length);
|
|
newBuckets[newBucketNo] = new Node(current.m_key, current.m_value, current.m_hashcode, newBuckets[newBucketNo]);
|
|
checked
|
{
|
newCountPerLock[newLockNo]++;
|
}
|
|
current = next;
|
}
|
}
|
|
// And finally adjust m_buckets and m_countPerLock to point to data for the new table
|
m_buckets = newBuckets;
|
m_countPerLock = newCountPerLock;
|
|
}
|
finally
|
{
|
// Release all locks that we took earlier
|
ReleaseLocks(0, locksAcquired);
|
}
|
}
|
|
/// <summary>
|
/// Computes the bucket and lock number for a particular key.
|
/// </summary>
|
private void GetBucketAndLockNo(
|
int hashcode, out int bucketNo, out int lockNo, int bucketCount)
|
{
|
bucketNo = (hashcode & 0x7fffffff) % bucketCount;
|
lockNo = bucketNo % m_locks.Length;
|
|
Assert(bucketNo >= 0 && bucketNo < bucketCount);
|
Assert(lockNo >= 0 && lockNo < m_locks.Length);
|
}
|
|
/// <summary>
|
/// The number of concurrent writes for which to optimize by default.
|
/// </summary>
|
private static int DefaultConcurrencyLevel
|
{
|
|
get { return DEFAULT_CONCURRENCY_MULTIPLIER * Environment.ProcessorCount; }
|
}
|
|
/// <summary>
|
/// Acquires all locks for this hash table, and increments locksAcquired by the number
|
/// of locks that were successfully acquired. The locks are acquired in an increasing
|
/// order.
|
/// </summary>
|
private void AcquireAllLocks(ref int locksAcquired)
|
{
|
AcquireLocks(0, m_locks.Length, ref locksAcquired);
|
Assert(locksAcquired == m_locks.Length);
|
}
|
|
/// <summary>
|
/// Acquires a contiguous range of locks for this hash table, and increments locksAcquired
|
/// by the number of locks that were successfully acquired. The locks are acquired in an
|
/// increasing order.
|
/// </summary>
|
private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired)
|
{
|
Assert(fromInclusive <= toExclusive);
|
|
for (int i = fromInclusive; i < toExclusive; i++)
|
{
|
bool lockTaken = false;
|
try
|
{
|
Monitor.Enter(m_locks[i]);
|
lockTaken = true;
|
}
|
finally
|
{
|
if (lockTaken)
|
{
|
locksAcquired++;
|
}
|
}
|
}
|
}
|
|
/// <summary>
|
/// Releases a contiguous range of locks.
|
/// </summary>
|
private void ReleaseLocks(int fromInclusive, int toExclusive)
|
{
|
Assert(fromInclusive <= toExclusive);
|
|
for (int i = fromInclusive; i < toExclusive; i++)
|
{
|
Monitor.Exit(m_locks[i]);
|
}
|
}
|
|
/// <summary>
|
/// Gets a collection containing the keys in the dictionary.
|
/// </summary>
|
private ReadOnlyCollection<TKey> GetKeys()
|
{
|
int locksAcquired = 0;
|
try
|
{
|
AcquireAllLocks(ref locksAcquired);
|
List<TKey> keys = new List<TKey>();
|
|
for (int i = 0; i < m_buckets.Length; i++)
|
{
|
Node current = m_buckets[i];
|
while (current != null)
|
{
|
keys.Add(current.m_key);
|
current = current.m_next;
|
}
|
}
|
|
return new ReadOnlyCollection<TKey>(keys);
|
}
|
finally
|
{
|
ReleaseLocks(0, locksAcquired);
|
}
|
}
|
|
/// <summary>
|
/// Gets a collection containing the values in the dictionary.
|
/// </summary>
|
private ReadOnlyCollection<TValue> GetValues()
|
{
|
int locksAcquired = 0;
|
try
|
{
|
AcquireAllLocks(ref locksAcquired);
|
List<TValue> values = new List<TValue>();
|
|
for (int i = 0; i < m_buckets.Length; i++)
|
{
|
Node current = m_buckets[i];
|
while (current != null)
|
{
|
values.Add(current.m_value);
|
current = current.m_next;
|
}
|
}
|
|
return new ReadOnlyCollection<TValue>(values);
|
}
|
finally
|
{
|
ReleaseLocks(0, locksAcquired);
|
}
|
}
|
|
/// <summary>
|
/// A helper method for asserts.
|
/// </summary>
|
[Conditional("DEBUG")]
|
private void Assert(bool condition)
|
{
|
if (!condition)
|
{
|
throw new Exception("Assertion failed.");
|
}
|
}
|
|
/// <summary>
|
/// A helper function to obtain the string for a particular resource key.
|
/// </summary>
|
/// <param name="key"></param>
|
/// <returns></returns>
|
private string GetResource(string key)
|
{
|
Assert(key != null);
|
|
return key;
|
}
|
|
/// <summary>
|
/// A node in a singly-linked list representing a particular hash table bucket.
|
/// </summary>
|
private class Node
|
{
|
internal TKey m_key;
|
internal TValue m_value;
|
internal volatile Node m_next;
|
internal int m_hashcode;
|
|
internal Node(TKey key, TValue value, int hashcode)
|
: this(key, value, hashcode, null)
|
{
|
}
|
|
internal Node(TKey key, TValue value, int hashcode, Node next)
|
{
|
m_key = key;
|
m_value = value;
|
m_next = next;
|
m_hashcode = hashcode;
|
}
|
}
|
|
/// <summary>
|
/// A private class to represent enumeration over the dictionary that implements the
|
/// IDictionaryEnumerator interface.
|
/// </summary>
|
private class DictionaryEnumerator : IDictionaryEnumerator
|
{
|
IEnumerator<KeyValuePair<TKey, TValue>> m_enumerator;
|
// Enumerator over the dictionary.
|
|
internal DictionaryEnumerator(ConcurrentDictionary<TKey, TValue> dictionary)
|
{
|
m_enumerator = dictionary.GetEnumerator();
|
}
|
|
public DictionaryEntry Entry
|
{
|
get { return new DictionaryEntry(m_enumerator.Current.Key, m_enumerator.Current.Value); }
|
}
|
|
public object Key
|
{
|
get { return m_enumerator.Current.Key; }
|
}
|
|
public object Value
|
{
|
get { return m_enumerator.Current.Value; }
|
}
|
|
public object Current
|
{
|
get { return this.Entry; }
|
}
|
|
public bool MoveNext()
|
{
|
return m_enumerator.MoveNext();
|
}
|
|
public void Reset()
|
{
|
m_enumerator.Reset();
|
}
|
}
|
|
/// <summary>
|
/// Get the data array to be serialized
|
/// </summary>
|
[OnSerializing]
|
private void OnSerializing(StreamingContext context)
|
{
|
// save the data into the serialization array to be saved
|
m_serializationArray = ToArray();
|
m_serializationConcurrencyLevel = m_locks.Length;
|
m_serializationCapacity = m_buckets.Length;
|
}
|
|
/// <summary>
|
/// Construct the dictionary from a previously seiralized one
|
/// </summary>
|
[OnDeserialized]
|
private void OnDeserialized(StreamingContext context)
|
{
|
KeyValuePair<TKey, TValue>[] array = m_serializationArray;
|
|
m_buckets = new Node[m_serializationCapacity];
|
m_countPerLock = new int[m_serializationConcurrencyLevel];
|
|
m_locks = new object[m_serializationConcurrencyLevel];
|
for (int i = 0; i < m_locks.Length; i++)
|
{
|
m_locks[i] = new object();
|
}
|
|
InitializeFromCollection(array);
|
m_serializationArray = null;
|
|
}
|
}
|
}
|
|
#endif
|