You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
581 lines
18 KiB
581 lines
18 KiB
5 years ago
|
#if NET20 || NET30 || NET35 || !NET_4_6
|
||
|
|
||
|
using System.Collections.Generic;
|
||
|
using LinqInternal.Collections;
|
||
|
using LinqInternal.Collections.Specialized;
|
||
|
using LinqInternal.Collections.ThreadSafe;
|
||
|
|
||
|
namespace System.Collections.Concurrent
|
||
|
{
|
||
|
[SerializableAttribute]
|
||
|
public class ConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
|
||
|
{
|
||
|
private readonly ValueCollection<TKey, TValue> _valueCollection;
|
||
|
private readonly SafeDictionary<TKey, TValue> _wrapped;
|
||
|
|
||
|
public ConcurrentDictionary()
|
||
|
: this(4, 31, EqualityComparer<TKey>.Default)
|
||
|
{
|
||
|
//Empty
|
||
|
}
|
||
|
|
||
|
public ConcurrentDictionary(int concurrencyLevel, int capacity)
|
||
|
: this(concurrencyLevel, capacity, EqualityComparer<TKey>.Default)
|
||
|
{
|
||
|
//Empty
|
||
|
}
|
||
|
|
||
|
public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
|
||
|
: this(4, 31, EqualityComparer<TKey>.Default)
|
||
|
{
|
||
|
if (ReferenceEquals(collection, null))
|
||
|
{
|
||
|
throw new ArgumentNullException("collection");
|
||
|
}
|
||
|
AddRange(collection);
|
||
|
}
|
||
|
|
||
|
public ConcurrentDictionary(IEqualityComparer<TKey> comparer)
|
||
|
: this(4, 31, comparer)
|
||
|
{
|
||
|
//Empty
|
||
|
}
|
||
|
|
||
|
public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
|
||
|
: this(4, 31, comparer)
|
||
|
{
|
||
|
if (ReferenceEquals(collection, null))
|
||
|
{
|
||
|
throw new ArgumentNullException("collection");
|
||
|
}
|
||
|
AddRange(collection);
|
||
|
}
|
||
|
|
||
|
public ConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer)
|
||
|
: this(concurrencyLevel, 31, comparer)
|
||
|
{
|
||
|
if (ReferenceEquals(collection, null))
|
||
|
{
|
||
|
throw new ArgumentNullException("collection");
|
||
|
}
|
||
|
AddRange(collection);
|
||
|
}
|
||
|
|
||
|
public ConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer)
|
||
|
{
|
||
|
if (ReferenceEquals(comparer, null))
|
||
|
{
|
||
|
throw new ArgumentNullException("comparer");
|
||
|
}
|
||
|
if (concurrencyLevel < 1)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException("concurrencyLevel", "concurrencyLevel < 1");
|
||
|
}
|
||
|
if (capacity < 0)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException("capacity", "capacity < 0");
|
||
|
}
|
||
|
_wrapped = new SafeDictionary<TKey, TValue>();
|
||
|
_valueCollection = new ValueCollection<TKey, TValue>(_wrapped);
|
||
|
}
|
||
|
|
||
|
public int Count
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
// This should be an snaptshot operation
|
||
|
return _wrapped.Count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool IsEmpty
|
||
|
{
|
||
|
get { return Count == 0; }
|
||
|
}
|
||
|
|
||
|
bool IDictionary.IsFixedSize
|
||
|
{
|
||
|
get { return false; }
|
||
|
}
|
||
|
|
||
|
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
|
||
|
{
|
||
|
get { return false; }
|
||
|
}
|
||
|
|
||
|
bool IDictionary.IsReadOnly
|
||
|
{
|
||
|
get { return false; }
|
||
|
}
|
||
|
|
||
|
bool ICollection.IsSynchronized
|
||
|
{
|
||
|
get { return false; }
|
||
|
}
|
||
|
|
||
|
public ICollection<TKey> Keys
|
||
|
{
|
||
|
get { return _wrapped.Keys; }
|
||
|
}
|
||
|
|
||
|
ICollection IDictionary.Keys
|
||
|
{
|
||
|
get { return (ICollection)_wrapped.Keys; }
|
||
|
}
|
||
|
|
||
|
object ICollection.SyncRoot
|
||
|
{
|
||
|
get { return this; }
|
||
|
}
|
||
|
|
||
|
public ICollection<TValue> Values
|
||
|
{
|
||
|
get { return _valueCollection; }
|
||
|
}
|
||
|
|
||
|
ICollection IDictionary.Values
|
||
|
{
|
||
|
get { return _valueCollection; }
|
||
|
}
|
||
|
|
||
|
public TValue this[TKey key]
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
// key can be null
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
return _wrapped[key];
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
_wrapped.Set(key, value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
object IDictionary.this[object key]
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (key == null)
|
||
|
{
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
// keep the is operator
|
||
|
if (key is TKey)
|
||
|
{
|
||
|
TValue result;
|
||
|
if (_wrapped.TryGetValue((TKey)key, out result))
|
||
|
{
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
// keep the is operator
|
||
|
if (key is TKey && value is TValue)
|
||
|
{
|
||
|
this[(TKey)key] = (TValue)value;
|
||
|
}
|
||
|
throw new ArgumentException();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
_wrapped.AddNew(key, value);
|
||
|
}
|
||
|
|
||
|
void IDictionary.Add(object key, object value)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
// keep the is operator
|
||
|
if (key is TKey && value is TValue)
|
||
|
{
|
||
|
_wrapped.AddNew((TKey)key, (TValue)value);
|
||
|
}
|
||
|
throw new ArgumentException();
|
||
|
}
|
||
|
|
||
|
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
|
||
|
{
|
||
|
if (ReferenceEquals(item.Key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
// While technically item is not null and item.Key is not an argument...
|
||
|
// This is what happens when you do the call on Microsoft's implementation
|
||
|
throw CreateArgumentNullExceptionKey(item.Key);
|
||
|
}
|
||
|
_wrapped.AddNew(item.Key, item.Value);
|
||
|
}
|
||
|
|
||
|
public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
// addValueFactory and updateValueFactory are checked for null inside the call
|
||
|
var result = _wrapped.AddOrUpdate
|
||
|
(
|
||
|
key,
|
||
|
addValueFactory,
|
||
|
updateValueFactory
|
||
|
);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
public TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
// updateValueFactory is checked for null inside the call
|
||
|
var result = _wrapped.AddOrUpdate
|
||
|
(
|
||
|
key,
|
||
|
addValue,
|
||
|
updateValueFactory
|
||
|
);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
public void Clear()
|
||
|
{
|
||
|
// This should be an snaptshot operation
|
||
|
_wrapped.Clear();
|
||
|
}
|
||
|
|
||
|
bool IDictionary.Contains(object key)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
// keep the is operator
|
||
|
if (key is TKey)
|
||
|
{
|
||
|
return ContainsKey((TKey)key);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
|
||
|
{
|
||
|
if (ReferenceEquals(item.Key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
// While technically item is not null and item.Key is not an argument...
|
||
|
// This is what happens when you do the call on Microsoft's implementation
|
||
|
throw CreateArgumentNullExceptionKey(item.Key);
|
||
|
}
|
||
|
TValue found;
|
||
|
if (_wrapped.TryGetValue(item.Key, out found))
|
||
|
{
|
||
|
if (EqualityComparer<TValue>.Default.Equals(found, item.Value))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public bool ContainsKey(TKey key)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
// No existing value is set, so no locking, right?
|
||
|
return _wrapped.ContainsKey(key);
|
||
|
}
|
||
|
|
||
|
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||
|
{
|
||
|
// This should be an snaptshot operation
|
||
|
Extensions.CanCopyTo(_wrapped.Count, array, arrayIndex);
|
||
|
this.CopyTo(array, arrayIndex);
|
||
|
}
|
||
|
|
||
|
void ICollection.CopyTo(Array array, int index)
|
||
|
{
|
||
|
// WORST API EVER - I shouldn't be supporting this
|
||
|
// I'm checking size before checking type - I have no plans to fix that
|
||
|
Extensions.CanCopyTo(_wrapped.Count, array, index);
|
||
|
try
|
||
|
{
|
||
|
var pairs = array as KeyValuePair<TKey, TValue>[]; // most decent alternative
|
||
|
if (pairs != null)
|
||
|
{
|
||
|
var keyValuePairs = pairs;
|
||
|
foreach (var pair in _wrapped)
|
||
|
{
|
||
|
keyValuePairs[index] = pair;
|
||
|
index++;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
var objects = array as object[];
|
||
|
if (objects != null)
|
||
|
{
|
||
|
var valuePairs = objects;
|
||
|
foreach (var pair in _wrapped)
|
||
|
{
|
||
|
valuePairs[index] = pair;
|
||
|
index++;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
var entries = array as DictionaryEntry[];
|
||
|
// that thing exists, I was totally unaware, I may as well use it.
|
||
|
if (entries != null)
|
||
|
{
|
||
|
var dictionaryEntries = entries;
|
||
|
foreach (var pair in _wrapped)
|
||
|
{
|
||
|
dictionaryEntries[index] = new DictionaryEntry(pair.Key, pair.Value);
|
||
|
index++;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
throw new ArgumentException("Not supported array type"); // A.K.A ScrewYouException
|
||
|
}
|
||
|
catch (IndexOutOfRangeException exception)
|
||
|
{
|
||
|
throw new ArgumentException("array", exception.Message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||
|
{
|
||
|
return _wrapped.GetEnumerator();
|
||
|
}
|
||
|
|
||
|
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||
|
{
|
||
|
return new DictionaryEnumerator(this);
|
||
|
}
|
||
|
|
||
|
IEnumerator IEnumerable.GetEnumerator()
|
||
|
{
|
||
|
return GetEnumerator();
|
||
|
}
|
||
|
|
||
|
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
|
||
|
{
|
||
|
// No existing value is set, so no locking, right?
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
// valueFactory is checked for null inside the call
|
||
|
return _wrapped.GetOrAdd(key, valueFactory);
|
||
|
}
|
||
|
|
||
|
public TValue GetOrAdd(TKey key, TValue value)
|
||
|
{
|
||
|
// No existing value is set, so no locking, right?
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
return _wrapped.GetOrAdd(key, value);
|
||
|
}
|
||
|
|
||
|
void IDictionary.Remove(object key)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
// keep the is operator
|
||
|
if (key is TKey)
|
||
|
{
|
||
|
_wrapped.Remove((TKey)key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
||
|
{
|
||
|
if (ReferenceEquals(item.Key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
// While technically item is not null and item.Key is not an argument...
|
||
|
// This is what happens when you do the call on Microsoft's implementation
|
||
|
throw CreateArgumentNullExceptionKey(item.Key);
|
||
|
}
|
||
|
TValue found;
|
||
|
return _wrapped.Remove(item.Key, input => EqualityComparer<TValue>.Default.Equals(item.Value, item.Value), out found);
|
||
|
}
|
||
|
|
||
|
bool IDictionary<TKey, TValue>.Remove(TKey key)
|
||
|
{
|
||
|
TValue bundle;
|
||
|
return TryRemove(key, out bundle);
|
||
|
}
|
||
|
|
||
|
public KeyValuePair<TKey, TValue>[] ToArray()
|
||
|
{
|
||
|
// This should be an snaptshot operation
|
||
|
var result = new List<KeyValuePair<TKey, TValue>>(_wrapped.Count);
|
||
|
foreach (var pair in _wrapped)
|
||
|
{
|
||
|
result.Add(pair);
|
||
|
}
|
||
|
return result.ToArray();
|
||
|
}
|
||
|
|
||
|
public bool TryAdd(TKey key, TValue value)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
return _wrapped.TryAdd(key, value);
|
||
|
}
|
||
|
|
||
|
public bool TryGetValue(TKey key, out TValue value)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
return _wrapped.TryGetValue(key, out value);
|
||
|
}
|
||
|
|
||
|
public bool TryRemove(TKey key, out TValue value)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
return _wrapped.Remove(key, out value);
|
||
|
}
|
||
|
|
||
|
public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
|
||
|
{
|
||
|
if (ReferenceEquals(key, null))
|
||
|
{
|
||
|
// ConcurrentDictionary hates null
|
||
|
throw new ArgumentNullException("key");
|
||
|
}
|
||
|
return _wrapped.TryUpdate(key, newValue, comparisonValue);
|
||
|
}
|
||
|
|
||
|
private static ArgumentNullException CreateArgumentNullExceptionKey(TKey key)
|
||
|
{
|
||
|
GC.KeepAlive(key);
|
||
|
return new ArgumentNullException("key");
|
||
|
}
|
||
|
|
||
|
private void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> collection)
|
||
|
{
|
||
|
foreach (var pair in collection)
|
||
|
{
|
||
|
if (!_wrapped.TryAdd(pair.Key, pair.Value))
|
||
|
{
|
||
|
throw new ArgumentException("The source contains duplicate keys.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private sealed class DictionaryEnumerator : IDictionaryEnumerator
|
||
|
{
|
||
|
private readonly IEnumerator<KeyValuePair<TKey, TValue>> _wrapped;
|
||
|
|
||
|
internal DictionaryEnumerator(ConcurrentDictionary<TKey, TValue> wrapped)
|
||
|
{
|
||
|
_wrapped = wrapped.GetEnumerator();
|
||
|
}
|
||
|
|
||
|
public object Current
|
||
|
{
|
||
|
get { return Entry; }
|
||
|
}
|
||
|
|
||
|
public DictionaryEntry Entry
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var current = _wrapped.Current;
|
||
|
return new DictionaryEntry(current.Key, current.Value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public object Key
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var current = _wrapped.Current;
|
||
|
return current.Key;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public object Value
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var current = _wrapped.Current;
|
||
|
return current.Value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool MoveNext()
|
||
|
{
|
||
|
if (_wrapped.MoveNext())
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
// The DictionaryEnumerator of ConcurrentDictionary is not IDisposable...
|
||
|
// Which means this method takes the responsability of disposing
|
||
|
_wrapped.Dispose();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public void Reset()
|
||
|
{
|
||
|
// This is two levels deep of you should not be using this.
|
||
|
// You should not be using DictionaryEnumerator
|
||
|
// And you should not be calling Reset
|
||
|
throw new NotSupportedException();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|