1459 lines
64 KiB
C#
1459 lines
64 KiB
C#
// Copyright (c) Vladimir Sadov. All rights reserved.
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
|
|
#nullable disable
|
|
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
|
|
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
using static System.Collections.Concurrent.DictionaryImpl;
|
|
|
|
namespace System.Collections.Concurrent
|
|
{
|
|
/// <summary>
|
|
/// Represents a thread-safe and lock-free 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="NonBlockingDictionary{TKey,TValue}"/> are thread-safe and may be used
|
|
/// concurrently from multiple threads.
|
|
/// </remarks>
|
|
[DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))]
|
|
[DebuggerDisplay("Count = {Count}")]
|
|
public class NonBlockingDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue> where TKey : notnull
|
|
{
|
|
private readonly bool _valueIsValueType = typeof(TValue).IsValueType;
|
|
internal DictionaryImpl<TKey, TValue> _table;
|
|
internal uint _lastResizeTickMillis;
|
|
internal object _sweeperInstance;
|
|
internal int _sweepRequests;
|
|
|
|
/// <summary>The default capacity, i.e. the initial # of buckets.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
private const int DefaultCapacity = 0;
|
|
|
|
/// <summary>Concurrency level is ignored. However it must be > 0.</summary>
|
|
private static int DefaultConcurrencyLevel => 1;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NonBlockingDictionary{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 NonBlockingDictionary() : this(DefaultConcurrencyLevel, DefaultCapacity, null) { }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NonBlockingDictionary{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="NonBlockingDictionary{TKey,TValue}"/> concurrently.</param>
|
|
/// <param name="capacity">The initial number of elements that the <see cref="NonBlockingDictionary{TKey,TValue}"/> can contain.</param>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1.</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException"> <paramref name="capacity"/> is less than 0.</exception>
|
|
public NonBlockingDictionary(int concurrencyLevel, int capacity) : this(concurrencyLevel, capacity, null) { }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NonBlockingDictionary{TKey,TValue}"/>
|
|
/// class that contains elements copied from the specified <see cref="IEnumerable{T}"/>, 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="IEnumerable{T}"/> whose elements are copied to the new <see cref="NonBlockingDictionary{TKey,TValue}"/>.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentException"><paramref name="collection"/> contains one or more duplicate keys.</exception>
|
|
public NonBlockingDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, null) { }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NonBlockingDictionary{TKey,TValue}"/>
|
|
/// class that is empty, has the specified concurrency level and capacity, and uses the specified
|
|
/// <see cref="IEqualityComparer{TKey}"/>.
|
|
/// </summary>
|
|
/// <param name="comparer">The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.</param>
|
|
public NonBlockingDictionary(IEqualityComparer<TKey>? comparer) : this(DefaultConcurrencyLevel, DefaultCapacity, comparer) { }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NonBlockingDictionary{TKey,TValue}"/>
|
|
/// class that contains elements copied from the specified <see cref="IEnumerable"/>, has the default concurrency
|
|
/// level, has the default initial capacity, and uses the specified <see cref="IEqualityComparer{TKey}"/>.
|
|
/// </summary>
|
|
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="NonBlockingDictionary{TKey,TValue}"/>.</param>
|
|
/// <param name="comparer">The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference (Nothing in Visual Basic).</exception>
|
|
public NonBlockingDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey>? comparer)
|
|
: this(comparer)
|
|
{
|
|
if (collection is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(collection));
|
|
}
|
|
|
|
InitializeFromCollection(collection);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NonBlockingDictionary{TKey,TValue}"/>
|
|
/// class that contains elements copied from the specified <see cref="IEnumerable"/>,
|
|
/// has the specified concurrency level, has the specified initial capacity, and uses the specified
|
|
/// <see cref="IEqualityComparer{TKey}"/>.
|
|
/// </summary>
|
|
/// <param name="concurrencyLevel">
|
|
/// The estimated number of threads that will update the <see cref="NonBlockingDictionary{TKey,TValue}"/> concurrently.
|
|
/// </param>
|
|
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied to the new
|
|
/// <see cref="NonBlockingDictionary{TKey,TValue}"/>.</param>
|
|
/// <param name="comparer">The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1.</exception>
|
|
/// <exception cref="ArgumentException"><paramref name="collection"/> contains one or more duplicate keys.</exception>
|
|
public NonBlockingDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey>? comparer)
|
|
: this(concurrencyLevel, DefaultCapacity, comparer)
|
|
{
|
|
if (collection is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(collection));
|
|
}
|
|
|
|
InitializeFromCollection(collection);
|
|
}
|
|
|
|
private void InitializeFromCollection(IEnumerable<KeyValuePair<TKey, TValue>> collection)
|
|
{
|
|
foreach (KeyValuePair<TKey, TValue> pair in collection)
|
|
{
|
|
if (pair.Key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (!this.TryAdd(pair.Key, pair.Value))
|
|
{
|
|
throw new ArgumentException("duplicate keys");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NonBlockingDictionary{TKey,TValue}"/>
|
|
/// class that is empty, has the specified concurrency level, has the specified initial capacity, and
|
|
/// uses the specified <see cref="IEqualityComparer{TKey}"/>.
|
|
/// </summary>
|
|
/// <param name="concurrencyLevel">The estimated number of threads that will update the <see cref="NonBlockingDictionary{TKey,TValue}"/> concurrently.</param>
|
|
/// <param name="capacity">The initial number of elements that the <see cref="NonBlockingDictionary{TKey,TValue}"/> can contain.</param>
|
|
/// <param name="comparer">The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.</param>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1. -or- <paramref name="capacity"/> is less than 0.</exception>
|
|
public NonBlockingDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey>? comparer)
|
|
{
|
|
if (concurrencyLevel < 1)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(concurrencyLevel));
|
|
}
|
|
if (capacity < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(capacity));
|
|
}
|
|
|
|
// add some extra so that filled to capacity would be at 50% density
|
|
capacity = Math.Max(capacity, capacity * 2);
|
|
|
|
if (!typeof(TKey).IsValueType)
|
|
{
|
|
_table = new DictionaryImplRef<TKey, TKey, TValue>(capacity, this);
|
|
_table._keyComparer = comparer ?? EqualityComparer<TKey>.Default;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (typeof(TKey) == typeof(int) || (typeof(TKey) == typeof(uint) && comparer == null))
|
|
{
|
|
if (comparer == null)
|
|
{
|
|
_table = Unsafe.As<DictionaryImpl<TKey, TValue>>(new DictionaryImplIntNoComparer<TValue>(capacity, Unsafe.As<NonBlockingDictionary<int, TValue>>(this)));
|
|
}
|
|
else
|
|
{
|
|
_table = Unsafe.As<DictionaryImpl<TKey, TValue>>(new DictionaryImplInt<TValue>(capacity, Unsafe.As<NonBlockingDictionary<int, TValue>>(this)));
|
|
_table._keyComparer = comparer;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (typeof(TKey) == typeof(long) || (typeof(TKey) == typeof(ulong) && comparer == null))
|
|
{
|
|
if (comparer == null)
|
|
{
|
|
_table = Unsafe.As<DictionaryImpl<TKey, TValue>>(new DictionaryImplLongNoComparer<TValue>(capacity, Unsafe.As<NonBlockingDictionary<long, TValue>>(this)));
|
|
}
|
|
else
|
|
{
|
|
_table = Unsafe.As<DictionaryImpl<TKey, TValue>>(new DictionaryImplLong<TValue>(capacity, Unsafe.As<NonBlockingDictionary<long, TValue>>(this)));
|
|
_table._keyComparer = comparer;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (typeof(TKey) == typeof(nint) || (typeof(TKey) == typeof(nuint) && comparer == null))
|
|
{
|
|
if (comparer == null)
|
|
{
|
|
_table = Unsafe.As<DictionaryImpl<TKey, TValue>>(new DictionaryImplNintNoComparer<TValue>(capacity, Unsafe.As<NonBlockingDictionary<nint, TValue>>(this)));
|
|
}
|
|
else
|
|
{
|
|
_table = Unsafe.As<DictionaryImpl<TKey, TValue>>(new DictionaryImplNint<TValue>(capacity, Unsafe.As<NonBlockingDictionary<nint, TValue>>(this)));
|
|
_table._keyComparer = comparer;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
_table = new DictionaryImplBoxed<TKey, TValue>(capacity, this);
|
|
_table._keyComparer = comparer ?? EqualityComparer<TKey>.Default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to add the specified key and value to the <see cref="NonBlockingDictionary{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="NonBlockingDictionary{TKey, TValue}"/> successfully; otherwise, false.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null reference (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="OverflowException">The <see cref="NonBlockingDictionary{TKey, TValue}"/> contains too many elements.</exception>
|
|
public bool TryAdd(TKey key, TValue value)
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
TValue oldVal = default;
|
|
return _table.PutIfMatch(key, value, ref oldVal, ValueMatch.NullOrDead);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the <see cref="NonBlockingDictionary{TKey, TValue}"/> contains the specified key.
|
|
/// </summary>
|
|
/// <param name="key">The key to locate in the <see cref="NonBlockingDictionary{TKey, TValue}"/>.</param>
|
|
/// <returns>true if the <see cref="NonBlockingDictionary{TKey, TValue}"/> contains an element with the specified key; otherwise, false.</returns>
|
|
/// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference (Nothing in Visual Basic).</exception>
|
|
public bool ContainsKey(TKey key)
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
object oldValObj = _table.TryGetValue(key);
|
|
Debug.Assert(!(oldValObj is Prime));
|
|
|
|
return oldValObj != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to remove and return the value with the specified key from the <see cref="NonBlockingDictionary{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="NonBlockingDictionary{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="ArgumentNullException"><paramref name="key"/> is a null reference (Nothing in Visual Basic).</exception>
|
|
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NETCOREAPP
|
|
public bool TryRemove(TKey key, [MaybeNullWhen(false)] out TValue value)
|
|
#else
|
|
public bool TryRemove(TKey key, out TValue value)
|
|
#endif
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
value = default;
|
|
return _table.RemoveIfMatch(key, ref value, ValueMatch.NotNullOrDead);
|
|
}
|
|
|
|
/// <summary>Removes a key and value from the dictionary.</summary>
|
|
/// <param name="item">The <see cref="KeyValuePair{TKey,TValue}"/> representing the key and value to remove.</param>
|
|
/// <returns>
|
|
/// true if the key and value represented by <paramref name="item"/> are successfully
|
|
/// found and removed; otherwise, false.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Both the specifed key and value must match the entry in the dictionary for it to be removed.
|
|
/// The key is compared using the dictionary's comparer (or the default comparer for <typeparamref name="TKey"/>
|
|
/// if no comparer was provided to the dictionary when it was constructed). The value is compared using the
|
|
/// default comparer for <typeparamref name="TValue"/>.
|
|
/// </remarks>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// The <see cref="KeyValuePair{TKey, TValue}.Key"/> property of <paramref name="item"/> is a null reference.
|
|
/// </exception>
|
|
public bool TryRemove(KeyValuePair<TKey, TValue> item)
|
|
{
|
|
if (item.Key is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(item));
|
|
}
|
|
|
|
TValue oldVal = item.Value;
|
|
return _table.RemoveIfMatch(item.Key, ref oldVal, ValueMatch.OldValue);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private TValue FromObjectValue(object obj)
|
|
{
|
|
// regular value type
|
|
if (default(TValue) != null)
|
|
{
|
|
return Unsafe.As<Boxed<TValue>>(obj).Value;
|
|
}
|
|
|
|
// null
|
|
if (obj == NULLVALUE)
|
|
{
|
|
return default(TValue);
|
|
}
|
|
|
|
// ref type
|
|
if (!_valueIsValueType)
|
|
{
|
|
return Unsafe.As<object, TValue>(ref obj);
|
|
}
|
|
|
|
// nullable
|
|
return (TValue)obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to get the value associated with the specified key from the <see cref="NonBlockingDictionary{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="NonBlockingDictionary{TKey,TValue}"/> with the specified 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="NonBlockingDictionary{TKey,TValue}"/>; otherwise, false.</returns>
|
|
/// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference (Nothing in Visual Basic).</exception>
|
|
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
|
|
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
|
#else
|
|
public bool TryGetValue(TKey key, out TValue value)
|
|
#endif
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
object oldValObj = _table.TryGetValue(key);
|
|
|
|
Debug.Assert(!(oldValObj is Prime));
|
|
|
|
if (oldValObj != null)
|
|
{
|
|
value = FromObjectValue(oldValObj);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
value = default(TValue);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the value associated with <paramref name="key"/> to <paramref name="newValue"/> if the existing value is equal
|
|
/// to <paramref name="comparisonValue"/>.
|
|
/// </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="ArgumentNullException"><paramref name="key"/> is a null reference.</exception>
|
|
public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
TValue oldVal = comparisonValue;
|
|
return _table.PutIfMatch(key, newValue, ref oldVal, ValueMatch.OldValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all keys and values from the <see cref="NonBlockingDictionary{TKey,TValue}"/>.
|
|
/// </summary>
|
|
public void Clear() => _table.Clear();
|
|
|
|
/// <summary>
|
|
/// Copies the elements of the <see cref="ICollection{T}"/> to an array of type <see cref="KeyValuePair{TKey,TValue}"/>,
|
|
/// starting at the specified array index.
|
|
/// </summary>
|
|
/// <param name="array">
|
|
/// The one-dimensional array of type <see cref="KeyValuePair{TKey,TValue}"/> that is the destination of the <see
|
|
/// cref="KeyValuePair{TKey,TValue}"/> elements copied from the <see cref="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="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.</exception>
|
|
/// <exception cref="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="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)
|
|
{
|
|
CopyToPairs(array, index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the key and value pairs stored in the <see cref="NonBlockingDictionary{TKey,TValue}"/> to a
|
|
/// new array.
|
|
/// </summary>
|
|
/// <returns>A new array containing a snapshot of key and value pairs copied from the <see cref="NonBlockingDictionary{TKey,TValue}"/>.
|
|
/// </returns>
|
|
public KeyValuePair<TKey, TValue>[] ToArray()
|
|
{
|
|
var snapshot = _table.GetSnapshot();
|
|
|
|
int count = snapshot.Count;
|
|
if (count == 0)
|
|
{
|
|
return Array.Empty<KeyValuePair<TKey, TValue>>();
|
|
}
|
|
|
|
var array = new KeyValuePair<TKey, TValue>[count];
|
|
int idx = 0;
|
|
while (snapshot.MoveNext() && idx < array.Length)
|
|
{
|
|
array[idx++] = snapshot.Current;
|
|
}
|
|
|
|
if (idx != array.Length)
|
|
{
|
|
Array.Resize(ref array, idx);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
/// <summary>Copy dictionary contents to an array.</summary>
|
|
private void CopyToPairs(KeyValuePair<TKey, TValue>[] array, int index)
|
|
{
|
|
if (array is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(array));
|
|
}
|
|
|
|
if (index < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
}
|
|
|
|
var length = array.Length;
|
|
if (index > length)
|
|
{
|
|
throw new ArgumentException("array is too short");
|
|
}
|
|
|
|
foreach (var entry in this)
|
|
{
|
|
if ((uint)index < (uint)length)
|
|
{
|
|
array[index++] = entry;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException("array is too short");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Copy dictionary contents to an array.</summary>
|
|
private void CopyToEntries(DictionaryEntry[] array, int index)
|
|
{
|
|
if (array is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(array));
|
|
}
|
|
|
|
if (index < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
}
|
|
|
|
var length = array.Length;
|
|
if (index >= length)
|
|
{
|
|
throw new ArgumentException("array is too short");
|
|
}
|
|
|
|
foreach (var entry in this)
|
|
{
|
|
if ((uint)index < (uint)length)
|
|
{
|
|
array[index++] = new DictionaryEntry(entry.Key, entry.Value);
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException("array is too short");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Copy dictionary contents to an array.</summary>
|
|
private void CopyToObjects(object[] array, int index)
|
|
{
|
|
if (array is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(array));
|
|
}
|
|
|
|
if (index < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
}
|
|
|
|
var length = array.Length;
|
|
if (index > length)
|
|
{
|
|
throw new ArgumentException("array is too short");
|
|
}
|
|
|
|
foreach (var entry in this)
|
|
{
|
|
if ((uint)index < (uint)length)
|
|
{
|
|
array[index++] = entry;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException("array is too short");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Returns an enumerator that iterates through the <see
|
|
/// cref="NonBlockingDictionary{TKey,TValue}"/>.</summary>
|
|
/// <returns>An enumerator for the <see cref="NonBlockingDictionary{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()
|
|
{
|
|
return new SnapshotEnumerator(_table.GetSnapshot());
|
|
}
|
|
|
|
/// <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="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.
|
|
/// </value>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="key"/> is a null reference (Nothing in Visual Basic).
|
|
/// </exception>
|
|
/// <exception cref="KeyNotFoundException">
|
|
/// The property is retrieved and <paramref name="key"/> does not exist in the collection.
|
|
/// </exception>
|
|
public TValue this[TKey key]
|
|
{
|
|
get
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
object oldValObj = _table.TryGetValue(key);
|
|
Debug.Assert(oldValObj is not Prime);
|
|
|
|
return oldValObj != null
|
|
? FromObjectValue(oldValObj)
|
|
: ThrowKeyNotFoundException();
|
|
}
|
|
set
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
TValue oldVal = default;
|
|
_table.PutIfMatch(key, value, ref oldVal, ValueMatch.Any);
|
|
}
|
|
}
|
|
|
|
/// <summary>Throws a KeyNotFoundException.</summary>
|
|
/// <remarks>Separate from ThrowHelper to avoid boxing at call site while reusing this generic instantiation.</remarks>
|
|
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
|
|
[DoesNotReturn]
|
|
#endif
|
|
private static TValue ThrowKeyNotFoundException() =>
|
|
throw new KeyNotFoundException();
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="IEqualityComparer{TKey}" />
|
|
/// that is used to determine equality of keys for the dictionary.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The <see cref="IEqualityComparer{TKey}" /> generic interface implementation
|
|
/// that is used to determine equality of keys for the current
|
|
/// <see cref="NonBlockingDictionary{TKey, TValue}" /> and to provide hash values for the keys.
|
|
/// </value>
|
|
/// <remarks>
|
|
/// <see cref="NonBlockingDictionary{TKey, TValue}" /> requires an equality implementation to determine
|
|
/// whether keys are equal. You can specify an implementation of the <see cref="IEqualityComparer{TKey}" />
|
|
/// generic interface by using a constructor that accepts a comparer parameter;
|
|
/// if you do not specify one, the default generic equality comparer <see cref="EqualityComparer{TKey}.Default" /> is used.
|
|
/// </remarks>
|
|
public IEqualityComparer<TKey> Comparer => _table._keyComparer ?? EqualityComparer<TKey>.Default;
|
|
|
|
/// <summary>
|
|
/// Gets the number of key/value pairs contained in the <see
|
|
/// cref="NonBlockingDictionary{TKey,TValue}"/>.
|
|
/// </summary>
|
|
/// <exception cref="OverflowException">The dictionary contains too many
|
|
/// elements.</exception>
|
|
/// <value>The number of key/value pairs contained in the <see
|
|
/// cref="NonBlockingDictionary{TKey,TValue}"/>.</value>
|
|
/// <remarks>Count has snapshot semantics and represents the number of items in the <see
|
|
/// cref="NonBlockingDictionary{TKey,TValue}"/>
|
|
/// at the moment when Count was accessed.</remarks>
|
|
public int Count => _table.Count;
|
|
|
|
/// <summary>
|
|
/// Adds a key/value pair to the <see cref="NonBlockingDictionary{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="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentNullException"><paramref name="valueFactory"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="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 is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (valueFactory is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(valueFactory));
|
|
}
|
|
|
|
return _table.GetOrAdd(key, valueFactory);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a key/value pair to the <see cref="NonBlockingDictionary{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>
|
|
/// <param name="factoryArgument">An argument value to pass into <paramref name="valueFactory"/>.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentNullException"><paramref name="valueFactory"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="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<TArg>(TKey key, Func<TKey, TArg, TValue> valueFactory, TArg factoryArgument)
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (valueFactory is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(valueFactory));
|
|
}
|
|
|
|
object oldValObj = _table.TryGetValue(key);
|
|
Debug.Assert(!(oldValObj is Prime));
|
|
|
|
if (oldValObj != null)
|
|
{
|
|
return FromObjectValue(oldValObj);
|
|
}
|
|
else
|
|
{
|
|
TValue newValue = valueFactory(key, factoryArgument);
|
|
TValue oldVal = default;
|
|
if (_table.PutIfMatch(key, newValue, ref oldVal, ValueMatch.NullOrDead))
|
|
{
|
|
return newValue;
|
|
}
|
|
else
|
|
{
|
|
return oldVal;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a key/value pair to the <see cref="NonBlockingDictionary{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="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="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 is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
TValue oldVal = default;
|
|
if (_table.PutIfMatch(key, value, ref oldVal, ValueMatch.NullOrDead))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return oldVal;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Adds a key/value pair to the <see cref="NonBlockingDictionary{TKey,TValue}"/> if the key does not already
|
|
/// exist, or updates a key/value pair in the <see cref="NonBlockingDictionary{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>
|
|
/// <param name="factoryArgument">An argument to pass into <paramref name="addValueFactory"/> and <paramref name="updateValueFactory"/>.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentNullException"><paramref name="addValueFactory"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="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<TArg>(
|
|
TKey key, Func<TKey, TArg, TValue> addValueFactory, Func<TKey, TValue, TArg, TValue> updateValueFactory, TArg factoryArgument)
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (addValueFactory is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(addValueFactory));
|
|
}
|
|
|
|
if (updateValueFactory is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(updateValueFactory));
|
|
}
|
|
|
|
TValue tValue2;
|
|
while (true)
|
|
{
|
|
TValue tValue;
|
|
if (this.TryGetValue(key, out tValue))
|
|
{
|
|
tValue2 = updateValueFactory(key, tValue, factoryArgument);
|
|
if (this.TryUpdate(key, tValue2, tValue))
|
|
{
|
|
return tValue2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tValue2 = addValueFactory(key, factoryArgument);
|
|
if (this.TryAdd(key, tValue2))
|
|
{
|
|
return tValue2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a key/value pair to the <see cref="NonBlockingDictionary{TKey,TValue}"/> if the key does not already
|
|
/// exist, or updates a key/value pair in the <see cref="NonBlockingDictionary{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="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentNullException"><paramref name="addValueFactory"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="OverflowException">The dictionary contains too many
|
|
/// elements.</exception>
|
|
/// <returns>The new value for the key. This will be either 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 is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (addValueFactory is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(addValueFactory));
|
|
}
|
|
|
|
if (updateValueFactory is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(updateValueFactory));
|
|
}
|
|
|
|
TValue tValue2;
|
|
while (true)
|
|
{
|
|
TValue tValue;
|
|
if (this.TryGetValue(key, out tValue))
|
|
{
|
|
tValue2 = updateValueFactory(key, tValue);
|
|
if (this.TryUpdate(key, tValue2, tValue))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tValue2 = addValueFactory(key);
|
|
if (this.TryAdd(key, tValue2))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return tValue2;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a key/value pair to the <see cref="NonBlockingDictionary{TKey,TValue}"/> if the key does not already
|
|
/// exist, or updates a key/value pair in the <see cref="NonBlockingDictionary{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="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="OverflowException">The dictionary contains too many
|
|
/// elements.</exception>
|
|
/// <returns>The new value for the key. This will be either the value of addValue (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 is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (updateValueFactory is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(updateValueFactory));
|
|
}
|
|
|
|
TValue tValue2;
|
|
while (true)
|
|
{
|
|
TValue tValue;
|
|
if (this.TryGetValue(key, out tValue))
|
|
{
|
|
tValue2 = updateValueFactory(key, tValue);
|
|
if (this.TryUpdate(key, tValue2, tValue))
|
|
{
|
|
return tValue2;
|
|
}
|
|
}
|
|
else if (this.TryAdd(key, addValue))
|
|
{
|
|
return addValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value that indicates whether the <see cref="NonBlockingDictionary{TKey,TValue}"/> is empty.
|
|
/// </summary>
|
|
/// <value>true if the <see cref="NonBlockingDictionary{TKey,TValue}"/> is empty; otherwise,
|
|
/// false.</value>
|
|
public bool IsEmpty => _table.Count == 0;
|
|
#region IDictionary<TKey,TValue> members
|
|
|
|
/// <summary>
|
|
/// Adds the specified key and value to the <see
|
|
/// cref="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="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="OverflowException">The dictionary contains too many
|
|
/// elements.</exception>
|
|
/// <exception cref="ArgumentException">
|
|
/// An element with the same key already exists in the <see
|
|
/// cref="NonBlockingDictionary{TKey,TValue}"/>.</exception>
|
|
void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
|
|
{
|
|
if (!TryAdd(key, value))
|
|
{
|
|
throw new ArgumentException("duplicate key");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the element with the specified key from the <see
|
|
/// cref="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="IDictionary{TKey,TValue}"/>.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
bool IDictionary<TKey, TValue>.Remove(TKey key) => TryRemove(key, out _);
|
|
|
|
/// <summary>
|
|
/// Gets a collection containing the keys in the <see
|
|
/// cref="Dictionary{TKey,TValue}"/>.
|
|
/// </summary>
|
|
/// <value>An <see cref="ICollection{TKey}"/> containing the keys in the
|
|
/// <see cref="Dictionary{TKey,TValue}"/>.</value>
|
|
public ICollection<TKey> Keys => GetKeys();
|
|
|
|
/// <summary>
|
|
/// Gets an <see cref="IEnumerable{TKey}"/> containing the keys of
|
|
/// the <see cref="IReadOnlyDictionary{TKey,TValue}"/>.
|
|
/// </summary>
|
|
/// <value>An <see cref="IEnumerable{TKey}"/> containing the keys of
|
|
/// the <see cref="IReadOnlyDictionary{TKey,TValue}"/>.</value>
|
|
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => GetKeys();
|
|
|
|
/// <summary>
|
|
/// Gets a collection containing the values in the <see
|
|
/// cref="Dictionary{TKey,TValue}"/>.
|
|
/// </summary>
|
|
/// <value>An <see cref="ICollection{TValue}"/> containing the values in
|
|
/// the
|
|
/// <see cref="Dictionary{TKey,TValue}"/>.</value>
|
|
public ICollection<TValue> Values => GetValues();
|
|
|
|
/// <summary>
|
|
/// Gets an <see cref="IEnumerable{TValue}"/> containing the values
|
|
/// in the <see cref="IReadOnlyDictionary{TKey,TValue}"/>.
|
|
/// </summary>
|
|
/// <value>An <see cref="IEnumerable{TValue}"/> containing the
|
|
/// values in the <see cref="IReadOnlyDictionary{TKey,TValue}"/>.</value>
|
|
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => GetValues();
|
|
#endregion
|
|
|
|
#region ICollection<KeyValuePair<TKey,TValue>> Members
|
|
|
|
/// <summary>
|
|
/// Adds the specified value to the <see cref="ICollection{TValue}"/>
|
|
/// with the specified key.
|
|
/// </summary>
|
|
/// <param name="keyValuePair">The <see cref="KeyValuePair{TKey,TValue}"/>
|
|
/// structure representing the key and value to add to the <see
|
|
/// cref="Dictionary{TKey,TValue}"/>.</param>
|
|
/// <exception cref="ArgumentNullException">The <paramref name="keyValuePair"/> of <paramref
|
|
/// name="keyValuePair"/> is null.</exception>
|
|
/// <exception cref="OverflowException">The <see
|
|
/// cref="Dictionary{TKey,TValue}"/>
|
|
/// contains too many elements.</exception>
|
|
/// <exception cref="ArgumentException">An element with the same key already exists in the
|
|
/// <see cref="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="ICollection{T}"/>
|
|
/// contains a specific key and value.
|
|
/// </summary>
|
|
/// <param name="keyValuePair">The <see cref="KeyValuePair{TKey,TValue}"/>
|
|
/// structure to locate in the <see
|
|
/// cref="ICollection{TValue}"/>.</param>
|
|
/// <returns>true if the <paramref name="keyValuePair"/> is found in the <see
|
|
/// cref="ICollection{T}"/>; otherwise, false.</returns>
|
|
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair)
|
|
{
|
|
TValue value;
|
|
return TryGetValue(keyValuePair.Key, out value) &&
|
|
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="ICollection{T}"/> is
|
|
/// read-only; otherwise, false. For <see
|
|
/// cref="Dictionary{TKey,TValue}"/>, this property always returns
|
|
/// false.</value>
|
|
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
|
|
|
|
/// <summary>
|
|
/// Removes a key and value from the dictionary.
|
|
/// </summary>
|
|
/// <param name="keyValuePair">The <see
|
|
/// cref="KeyValuePair{TKey,TValue}"/>
|
|
/// structure representing the key and value to remove from the <see
|
|
/// cref="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="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) =>
|
|
TryRemove(keyValuePair);
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable Members
|
|
|
|
/// <summary>Returns an enumerator that iterates through the <see
|
|
/// cref="NonBlockingDictionary{TKey,TValue}"/>.</summary>
|
|
/// <returns>An enumerator for the <see cref="NonBlockingDictionary{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() => ((NonBlockingDictionary<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="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="OverflowException">The dictionary contains too many
|
|
/// elements.</exception>
|
|
/// <exception cref="ArgumentException">
|
|
/// <paramref name="key"/> is of a type that is not assignable to the key type <typeparamref
|
|
/// name="TKey"/> of the <see cref="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="Dictionary{TKey,TValue}"/>.
|
|
/// -or- A value with the same key already exists in the <see
|
|
/// cref="Dictionary{TKey,TValue}"/>.
|
|
/// </exception>
|
|
void IDictionary.Add(object key, object? value)
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (!(key is TKey))
|
|
{
|
|
throw new ArgumentException("type of key is incorrect");
|
|
}
|
|
|
|
ThrowIfInvalidObjectValue(value);
|
|
|
|
((IDictionary<TKey, TValue>)this).Add((TKey)key, (TValue)value!);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether the <see cref="IDictionary"/> contains an
|
|
/// element with the specified key.
|
|
/// </summary>
|
|
/// <param name="key">The key to locate in the <see
|
|
/// cref="IDictionary"/>.</param>
|
|
/// <returns>true if the <see cref="IDictionary"/> contains
|
|
/// an element with the specified key; otherwise, false.</returns>
|
|
/// <exception cref="ArgumentNullException"> <paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
bool IDictionary.Contains(object key)
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
return key is TKey tkey && ContainsKey(tkey);
|
|
}
|
|
|
|
/// <summary>Provides an <see cref="IDictionaryEnumerator"/> for the
|
|
/// <see cref="IDictionary"/>.</summary>
|
|
/// <returns>An <see cref="IDictionaryEnumerator"/> for the <see
|
|
/// cref="IDictionary"/>.</returns>
|
|
IDictionaryEnumerator IDictionary.GetEnumerator() => new SnapshotIDictionaryEnumerator(_table.GetSnapshot());
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the <see
|
|
/// cref="IDictionary"/> has a fixed size.
|
|
/// </summary>
|
|
/// <value>true if the <see cref="IDictionary"/> has a
|
|
/// fixed size; otherwise, false. For <see
|
|
/// cref="NonBlockingDictionary{TKey,TValue}"/>, this property always
|
|
/// returns false.</value>
|
|
bool IDictionary.IsFixedSize => false;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the <see
|
|
/// cref="IDictionary"/> is read-only.
|
|
/// </summary>
|
|
/// <value>true if the <see cref="IDictionary"/> is
|
|
/// read-only; otherwise, false. For <see
|
|
/// cref="NonBlockingDictionary{TKey,TValue}"/>, this property always
|
|
/// returns false.</value>
|
|
bool IDictionary.IsReadOnly => false;
|
|
|
|
/// <summary>
|
|
/// Gets an <see cref="ICollection"/> containing the keys of the <see
|
|
/// cref="IDictionary"/>.
|
|
/// </summary>
|
|
/// <value>An <see cref="ICollection"/> containing the keys of the <see
|
|
/// cref="IDictionary"/>.</value>
|
|
ICollection IDictionary.Keys => GetKeys();
|
|
|
|
/// <summary>
|
|
/// Removes the element with the specified key from the <see
|
|
/// cref="IDictionary"/>.
|
|
/// </summary>
|
|
/// <param name="key">The key of the element to remove.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
void IDictionary.Remove(object key)
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (key is TKey tkey)
|
|
{
|
|
TryRemove(tkey, out _);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an <see cref="ICollection"/> containing the values in the <see
|
|
/// cref="IDictionary"/>.
|
|
/// </summary>
|
|
/// <value>An <see cref="ICollection"/> containing the values in the <see
|
|
/// cref="IDictionary"/>.</value>
|
|
ICollection IDictionary.Values => 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="NonBlockingDictionary{TKey,TValue}"/>.</value>
|
|
/// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="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="NonBlockingDictionary{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="NonBlockingDictionary{TKey,TValue}"/>
|
|
/// </exception>
|
|
object? IDictionary.this[object key]
|
|
{
|
|
get
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (key is TKey tkey && TryGetValue(tkey, out TValue? value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
set
|
|
{
|
|
if (key is null)
|
|
{
|
|
ThrowHelper.ThrowKeyNullException();
|
|
}
|
|
|
|
if (!(key is TKey))
|
|
{
|
|
throw new ArgumentException("type of key is incorrect");
|
|
}
|
|
|
|
ThrowIfInvalidObjectValue(value);
|
|
|
|
((NonBlockingDictionary<TKey, TValue>)this)[(TKey)key] = (TValue)value!;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static void ThrowIfInvalidObjectValue(object? value)
|
|
{
|
|
if (value != null)
|
|
{
|
|
if (!(value is TValue))
|
|
{
|
|
ThrowHelper.ThrowValueNullException();
|
|
}
|
|
}
|
|
else if (default(TValue) != null)
|
|
{
|
|
ThrowHelper.ThrowValueNullException();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ICollection Members
|
|
|
|
/// <summary>
|
|
/// Copies the elements of the <see cref="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="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="ArgumentNullException"><paramref name="array"/> is a null reference
|
|
/// (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than
|
|
/// 0.</exception>
|
|
/// <exception cref="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="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 is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(array));
|
|
}
|
|
|
|
// 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
|
|
|
|
if (array is KeyValuePair<TKey, TValue>[] pairs)
|
|
{
|
|
CopyToPairs(pairs, index);
|
|
return;
|
|
}
|
|
|
|
if (array is DictionaryEntry[] entries)
|
|
{
|
|
CopyToEntries(entries, index);
|
|
return;
|
|
}
|
|
|
|
if (array is object[] objects)
|
|
{
|
|
CopyToObjects(objects, index);
|
|
return;
|
|
}
|
|
|
|
throw new ArgumentException("array type is incorrect", nameof(array));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether access to the <see cref="ICollection"/> is
|
|
/// synchronized with the SyncRoot.
|
|
/// </summary>
|
|
/// <value>true if access to the <see cref="ICollection"/> is synchronized
|
|
/// (thread safe); otherwise, false. For <see
|
|
/// cref="NonBlockingDictionary{TKey,TValue}"/>, this property always
|
|
/// returns false.</value>
|
|
bool ICollection.IsSynchronized => false;
|
|
|
|
/// <summary>
|
|
/// Gets an object that can be used to synchronize access to the <see
|
|
/// cref="ICollection"/>. This property is not supported.
|
|
/// </summary>
|
|
/// <exception cref="NotSupportedException">The SyncRoot property is not supported.</exception>
|
|
object ICollection.SyncRoot => throw new NotSupportedException("SyncRoot not supported");
|
|
|
|
#endregion
|
|
|
|
|
|
/// <summary>
|
|
/// Gets a collection containing the keys in the dictionary.
|
|
/// </summary>
|
|
private ReadOnlyCollection<TKey> GetKeys()
|
|
{
|
|
var keys = new List<TKey>(Count);
|
|
foreach (var kv in this)
|
|
{
|
|
keys.Add(kv.Key);
|
|
}
|
|
|
|
return new ReadOnlyCollection<TKey>(keys);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a collection containing the values in the dictionary.
|
|
/// </summary>
|
|
private ReadOnlyCollection<TValue> GetValues()
|
|
{
|
|
var values = new List<TValue>(Count);
|
|
foreach (var kv in this)
|
|
{
|
|
values.Add(kv.Value);
|
|
}
|
|
|
|
return new ReadOnlyCollection<TValue>(values);
|
|
}
|
|
|
|
internal class SnapshotEnumerator : IEnumerator<KeyValuePair<TKey, TValue>>
|
|
{
|
|
private DictionaryImpl<TKey, TValue>.Snapshot _snapshot;
|
|
public SnapshotEnumerator(DictionaryImpl<TKey, TValue>.Snapshot snapshot)
|
|
{
|
|
_snapshot = snapshot;
|
|
}
|
|
|
|
public KeyValuePair<TKey, TValue> Current => _snapshot.Current;
|
|
object IEnumerator.Current => _snapshot.Current;
|
|
|
|
public bool MoveNext() => _snapshot.MoveNext();
|
|
public void Reset() => _snapshot.Reset();
|
|
public void Dispose() { }
|
|
}
|
|
|
|
internal class SnapshotIDictionaryEnumerator : IDictionaryEnumerator
|
|
{
|
|
private DictionaryImpl<TKey, TValue>.Snapshot _snapshot;
|
|
public SnapshotIDictionaryEnumerator(DictionaryImpl<TKey, TValue>.Snapshot snapshot)
|
|
{
|
|
_snapshot = snapshot;
|
|
}
|
|
|
|
public DictionaryEntry Entry => _snapshot.Entry;
|
|
object IEnumerator.Current => _snapshot.Entry;
|
|
|
|
public object Key => _snapshot.Current.Key;
|
|
public object Value => _snapshot.Current.Value;
|
|
|
|
public bool MoveNext() => _snapshot.MoveNext();
|
|
public void Reset() => _snapshot.Reset();
|
|
public void Dispose() { }
|
|
}
|
|
}
|
|
|
|
internal sealed class IDictionaryDebugView<TKey, TValue> where TKey : notnull
|
|
{
|
|
private readonly IDictionary<TKey, TValue> _dictionary;
|
|
|
|
public IDictionaryDebugView(IDictionary<TKey, TValue> dictionary)
|
|
{
|
|
if (dictionary is null)
|
|
{
|
|
ThrowHelper.ThrowArgumentNullException(nameof(dictionary));
|
|
}
|
|
|
|
_dictionary = dictionary;
|
|
}
|
|
|
|
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
|
public KeyValuePair<TKey, TValue>[] Items
|
|
{
|
|
get
|
|
{
|
|
var items = new KeyValuePair<TKey, TValue>[_dictionary.Count];
|
|
_dictionary.CopyTo(items, 0);
|
|
return items;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static class ThrowHelper
|
|
{
|
|
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
|
|
[DoesNotReturn]
|
|
#endif
|
|
internal static void ThrowKeyNullException() => ThrowArgumentNullException("key");
|
|
|
|
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
|
|
[DoesNotReturn]
|
|
#endif
|
|
internal static void ThrowArgumentNullException(string name) => throw new ArgumentNullException(name);
|
|
|
|
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
|
|
[DoesNotReturn]
|
|
#endif
|
|
internal static void ThrowArgumentNullException(string name, string message) => throw new ArgumentNullException(name, message);
|
|
|
|
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
|
|
[DoesNotReturn]
|
|
#endif
|
|
internal static void ThrowValueNullException() => throw new ArgumentException("value is null");
|
|
|
|
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
|
|
[DoesNotReturn]
|
|
#endif
|
|
internal static void ThrowOutOfMemoryException() => throw new OutOfMemoryException();
|
|
}
|
|
}
|