/*
Copyright 2008 The 'A Concurrent Hashtable' development team
(http://www.codeplex.com/CH/People/ProjectPeople.aspx)
This library is licensed under the GNU Library General Public License (LGPL). You should
have received a copy of the license along with the source code. If not, an online copy
of the license can be found at http://www.codeplex.com/CH/license.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Collections;
namespace Orvid.Concurrent.Collections
{
///
/// Search key structure for
///
/// Type of the key.
/// Type of the value.
public struct ConcurrentDictionaryKey
{
internal TKey _Key;
internal TValue _Value;
internal bool _IgnoreValue;
internal ConcurrentDictionaryKey(TKey key)
{
_Key = key;
_IgnoreValue = true;
_Value = default(TValue);
}
internal ConcurrentDictionaryKey(TKey key, TValue value)
{
_Key = key;
_IgnoreValue = false ;
_Value = value;
}
}
///
/// A Concurrent implementation.
///
/// Type of the keys.
/// Type of the values.
///
/// This class is threadsafe and highly concurrent. This means that multiple threads can do lookup and insert operations
/// on this dictionary simultaneously.
/// It is not guaranteed that collisions will not occur. The dictionary is partitioned in segments. A segment contains
/// a set of items based on a hash of those items. The more segments there are and the beter the hash, the fewer collisions will occur.
/// This means that a nearly empty ConcurrentDictionary is not as concurrent as one containing many items.
///
[Serializable]
public class ConcurrentDictionary : ConcurrentHashtable?, ConcurrentDictionaryKey>, IDictionary, IDictionary, ISerializable
{
///
/// Constructs a instance using the default to compare keys.
///
public ConcurrentDictionary()
: this(EqualityComparer.Default)
{ }
///
/// Constructs a instance using the specified to compare keys.
///
/// The tp compare keys with.
/// is null.
public ConcurrentDictionary(IEqualityComparer comparer)
: base()
{
if (comparer == null)
throw new ArgumentNullException("comparer");
_Comparer = comparer;
Initialize();
}
ConcurrentDictionary(SerializationInfo serializationInfo, StreamingContext streamingContext)
{
_Comparer = (IEqualityComparer)serializationInfo.GetValue("Comparer", typeof(IEqualityComparer));
var items = (List>)serializationInfo.GetValue("Items", typeof(List>));
if (_Comparer == null || items == null)
throw new SerializationException();
Initialize();
foreach (var kvp in items)
this.Add(kvp);
}
#region Traits
readonly IEqualityComparer _Comparer;
///
/// Gives the of TKey that is used to compare keys.
///
public IEqualityComparer Comparer { get { return _Comparer; } }
///
/// Get a hashcode for given storeable item.
///
/// Reference to the item to get a hash value for.
/// The hash value as an .
///
/// The hash returned should be properly randomized hash. The standard GetItemHashCode methods are usually not good enough.
/// A storeable item and a matching search key should return the same hash code.
/// So the statement ItemEqualsItem(storeableItem, searchKey) ? GetItemHashCode(storeableItem) == GetItemHashCode(searchKey) : true should always be true;
///
internal protected override UInt32 GetItemHashCode(ref KeyValuePair? item)
{ return item.HasValue ? Hasher.Rehash(_Comparer.GetHashCode(item.Value.Key)) : 0; }
///
/// Get a hashcode for given search key.
///
/// Reference to the key to get a hash value for.
/// The hash value as an .
///
/// The hash returned should be properly randomized hash. The standard GetItemHashCode methods are usually not good enough.
/// A storeable item and a matching search key should return the same hash code.
/// So the statement ItemEqualsItem(storeableItem, searchKey) ? GetItemHashCode(storeableItem) == GetItemHashCode(searchKey) : true should always be true;
///
internal protected override UInt32 GetKeyHashCode(ref ConcurrentDictionaryKey key)
{ return Hasher.Rehash(_Comparer.GetHashCode(key._Key)); }
///
/// Compares a storeable item to a search key. Should return true if they match.
///
/// Reference to the storeable item to compare.
/// Reference to the search key to compare.
/// True if the storeable item and search key match; false otherwise.
internal protected override bool ItemEqualsKey(ref KeyValuePair? item, ref ConcurrentDictionaryKey key)
{ return item.HasValue && _Comparer.Equals(item.Value.Key, key._Key) && (key._IgnoreValue || EqualityComparer.Default.Equals(item.Value.Value, key._Value)); }
///
/// Compares two storeable items for equality.
///
/// Reference to the first storeable item to compare.
/// Reference to the second storeable item to compare.
/// True if the two soreable items should be regarded as equal.
internal protected override bool ItemEqualsItem(ref KeyValuePair? item1, ref KeyValuePair? item2)
{ return item1.HasValue && item2.HasValue && _Comparer.Equals(item1.Value.Key, item2.Value.Key); }
///
/// Indicates if a specific item reference contains a valid item.
///
/// The storeable item reference to check.
/// True if the reference doesn't refer to a valid item; false otherwise.
/// The statement IsEmpty(default(TStoredI)) should always be true.
internal protected override bool IsEmpty(ref KeyValuePair? item)
{ return !item.HasValue; }
protected internal override Type GetKeyType(ref KeyValuePair? item)
{ return !item.HasValue || item.Value.Key == null ? null : item.Value.Key.GetType(); }
#endregion
#region IDictionary Members
///
/// Adds an element with the provided key and value to the dictionary.
///
/// The object to use as the key of the element to add.
/// The object to use as the value of the element to add.
/// An element with the same key already exists in the dictionary.
void IDictionary.Add(TKey key, TValue value)
{ ((ICollection>)this).Add( new KeyValuePair(key,value) ); }
///
/// Determines whether the dictionary
/// contains an element with the specified key.
///
/// The key to locate in the dictionary.
/// true if the dictionary contains
/// an element with the key; otherwise, false.
public bool ContainsKey(TKey key)
{
KeyValuePair? presentItem;
ConcurrentDictionaryKey searchKey = new ConcurrentDictionaryKey(key);
return FindItem(ref searchKey, out presentItem);
}
///
/// Gets an containing the keys of
/// the dictionary.
///
/// An containing the keys of the dictionary.
/// This property takes a snapshot of the current keys collection of the dictionary at the moment of invocation.
public ICollection Keys
{
get
{
lock (SyncRoot)
return base.Items.Select(kvp => kvp.Value.Key).ToList();
}
}
///
/// Removes the element with the specified key from the dictionary.
///
/// The key of the element to remove.
/// true if the element is successfully removed; otherwise, false. This method
/// also returns false if key was not found in the original dictionary.
bool IDictionary.Remove(TKey key)
{
KeyValuePair? oldItem;
ConcurrentDictionaryKey searchKey = new ConcurrentDictionaryKey(key);
return base.RemoveItem(ref searchKey, out oldItem);
}
///
/// Gets the value associated with the specified key.
///
/// The key whose value to get.
///
/// When this method returns, the value associated with the specified key, if
/// the key is found; otherwise, the default value for the type of the value
/// parameter. This parameter is passed uninitialized.
///
///
/// true if the dictionary contains an element with the specified key; otherwise, false.
///
public bool TryGetValue(TKey key, out TValue value)
{
KeyValuePair? presentItem;
ConcurrentDictionaryKey searchKey = new ConcurrentDictionaryKey(key);
var res = FindItem(ref searchKey, out presentItem);
if (res)
{
value = presentItem.Value.Value;
return true;
}
else
{
value = default(TValue);
return false;
}
}
///
/// Gets an containing the values in
/// the dictionary.
///
///
/// An containing the values in the dictionary.
///
/// This property takes a snapshot of the current keys collection of the dictionary at the moment of invocation.
public ICollection Values
{
get
{
lock (SyncRoot)
return base.Items.Select(kvp => kvp.Value.Value).ToList();
}
}
///
/// Gets or sets the value associated with the specified key.
///
/// The key of the value to get or set.
/// The value associated with the specified key. If the specified key is not found, a get operation throws a KeyNotFoundException, and a set operation creates a new element with the specified key.
///
/// When working with multiple threads, that can each potentialy remove the searched for item, a can always be expected.
///
public TValue this[TKey key]
{
get
{
KeyValuePair? presentItem;
ConcurrentDictionaryKey searchKey = new ConcurrentDictionaryKey(key);
if (!FindItem(ref searchKey, out presentItem))
throw new KeyNotFoundException("The property is retrieved and key is not found.");
return presentItem.Value.Value;
}
set
{
KeyValuePair? newItem = new KeyValuePair(key, value);
KeyValuePair? presentItem;
InsertItem(ref newItem, out presentItem);
}
}
#endregion
#region IDictionary Members
void IDictionary.Add(object key, object value)
{ ((IDictionary)this).Add((TKey)key, (TValue)value); }
void IDictionary.Clear()
{ ((IDictionary)this).Clear(); }
bool IDictionary.Contains(object key)
{ return ((IDictionary)this).ContainsKey((TKey)key); }
class DictionaryEnumerator : IDictionaryEnumerator
{
public IEnumerator> _source;
#region IDictionaryEnumerator Members
DictionaryEntry IDictionaryEnumerator.Entry
{
get
{
var current = _source.Current;
return new DictionaryEntry(current.Key, current.Value);
}
}
object IDictionaryEnumerator.Key
{ get { return _source.Current.Key; } }
object IDictionaryEnumerator.Value
{ get { return _source.Current.Value; } }
#endregion
#region IEnumerator Members
object IEnumerator.Current
{ get { return ((IDictionaryEnumerator)this).Entry; } }
bool IEnumerator.MoveNext()
{ return _source.MoveNext(); }
void IEnumerator.Reset()
{ _source.Reset(); }
#endregion
}
IDictionaryEnumerator IDictionary.GetEnumerator()
{ return new DictionaryEnumerator { _source = ((IDictionary)this).GetEnumerator() }; }
bool IDictionary.IsFixedSize
{ get { return false; } }
bool IDictionary.IsReadOnly
{ get { return false; } }
ICollection IDictionary.Keys
{ get { return (ICollection)((IDictionary)this).Keys; } }
void IDictionary.Remove(object key)
{ ((IDictionary)this).Remove((TKey)key); }
ICollection IDictionary.Values
{ get { return (ICollection)((IDictionary)this).Values; } }
object IDictionary.this[object key]
{
get { return ((IDictionary)this)[(TKey)key]; }
set { ((IDictionary)this)[(TKey)key] = (TValue)value; }
}
#endregion
#region ICollection> Members
///
/// Adds an association to the dictionary.
///
/// A that represents the association to add.
/// An association with an equal key already exists in the dicitonary.
void ICollection>.Add(KeyValuePair item)
{
KeyValuePair? newItem = item;
KeyValuePair? presentItem;
if (GetOldestItem(ref newItem, out presentItem))
throw new ArgumentException("An element with the same key already exists.");
}
///
/// Removes all items from the dictionary.
///
/// WHen working with multiple threads, that each can add items to this dictionary, it is not guaranteed that the dictionary will be empty when this method returns.
public new void Clear()
{ base.Clear(); }
///
/// Determines whether the specified association exists in the dictionary.
///
/// The key-value association to search fo in the dicionary.
/// True if item is found in the dictionary; otherwise, false.
///
/// This method compares both key and value. It uses the default equality comparer to compare values.
///
bool ICollection>.Contains(KeyValuePair item)
{
KeyValuePair? presentItem;
ConcurrentDictionaryKey searchKey = new ConcurrentDictionaryKey(item.Key,item.Value);
return
FindItem(ref searchKey, out presentItem);
}
///
/// Copies all associations of the dictionary to an
/// , starting at a particular index.
///
/// The one-dimensional that is the destination of the associations
/// copied from . The must
/// have zero-based indexing.
/// The zero-based index in at which copying begins.
/// is null.
/// is less than 0.
/// is equal to or greater than the length of .
/// The number of associations to be copied
/// is greater than the available space from to the end of the destination
/// .
void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
{
lock (SyncRoot)
Items.Select(nkvp => nkvp.Value).ToList().CopyTo(array, arrayIndex);
}
///
/// Gets the number of elements contained in the .
///
public new int Count
{ get { return base.Count; } }
///
/// Gets a value indicating whether the is read-only, which is always false.
///
bool ICollection>.IsReadOnly
{ get { return false; } }
///
/// Removes the specified association from the , comparing both key and value.
///
/// A representing the association to remove.
/// true if the association was successfully removed from the ;
/// otherwise, false. This method also returns false if the association is not found in
/// the original .
///
bool ICollection>.Remove(KeyValuePair item)
{
KeyValuePair? oldItem;
ConcurrentDictionaryKey searchKey = new ConcurrentDictionaryKey(item.Key,item.Value);
return base.RemoveItem(ref searchKey, out oldItem);
}
#endregion
#region ICollection Members
void ICollection.CopyTo(Array array, int index)
{ ((ICollection>)this).CopyTo((KeyValuePair[])array, index); }
int ICollection.Count
{ get { return ((ICollection>)this).Count; } }
bool ICollection.IsSynchronized
{ get { return true; } }
object ICollection.SyncRoot
{ get { return this; } }
#endregion
#region IEnumerable> Members
///
/// Returns an enumerator that iterates through all associations in the at the moment of invocation.
///
/// A that can be used to iterate through the associations.
public IEnumerator> GetEnumerator()
{
lock (SyncRoot)
return Items.Select(nkvp => nkvp.Value).ToList().GetEnumerator();
}
#endregion
#region IEnumerable Members
///
/// Returns an enumerator that iterates through all associations in the at the moment of invocation.
///
/// A that can be used to iterate through the associations.
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
#endregion
#region ISerializable Members
[SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Items", (object)Items.Select(item => item.Value).ToList());
info.AddValue("Comparer", _Comparer);
}
#endregion
public TValue AddOrUpdate(TKey key, Func addValueFactory, Func updateValueFactory)
{
if (null == addValueFactory)
throw new ArgumentNullException("addValueFactory");
if (null == updateValueFactory)
throw new ArgumentNullException("updateValueFactory");
var searchKey = new ConcurrentDictionaryKey(key);
KeyValuePair? latestItem;
while (true)
{
if (this.FindItem(ref searchKey, out latestItem))
{
TValue storedValue = latestItem.Value.Value;
TValue newValue = updateValueFactory(key, storedValue);
if (TryUpdate(key, newValue, storedValue))
{
return newValue;
}
}
else
{
return AddOrUpdate(key, addValueFactory(key), updateValueFactory);
}
}
}
public TValue AddOrUpdate(TKey key, TValue addValue, Func updateValueFactory)
{
if (null == updateValueFactory)
{
throw new ArgumentNullException("updateValueFactory");
}
KeyValuePair? latestItem;
KeyValuePair? addItem = new KeyValuePair(key, addValue);
while(true)
{
if (this.GetOldestItem(ref addItem, out latestItem))
{
TValue storedValue = latestItem.Value.Value;
TValue newValue = updateValueFactory(key, storedValue);
if (TryUpdate(key, newValue, storedValue))
{
return newValue;
}
}
else
{
return latestItem.Value.Value;
}
}
}
public bool TryAdd(TKey key, TValue value)
{
KeyValuePair? addKey = new KeyValuePair(key, value);
KeyValuePair? oldItem;
return !this.GetOldestItem(ref addKey, out oldItem);
}
public bool TryRemove(TKey key, out TValue value)
{
var searchKey = new ConcurrentDictionaryKey(key);
KeyValuePair? oldItem;
var res = base.RemoveItem(ref searchKey, out oldItem);
value = res ? oldItem.Value.Value : default(TValue);
return res;
}
public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
{
var searchKey = new ConcurrentDictionaryKey(key);
KeyValuePair? newItem = new KeyValuePair(key, newValue);
KeyValuePair? dummy;
return base.ReplaceItem(ref searchKey, ref newItem, out dummy, item => EqualityComparer.Default.Equals(item.Value.Value, comparisonValue));
}
public TValue GetOrAdd(TKey key, TValue value)
{
KeyValuePair? newItem = new KeyValuePair(key, value);
KeyValuePair? oldItem;
return base.GetOldestItem(ref newItem, out oldItem) ? oldItem.Value.Value : value;
}
public TValue GetOrAdd(TKey key, Func valueFactory)
{
if (null == valueFactory)
throw new ArgumentNullException("valueFactory");
var searchKey = new ConcurrentDictionaryKey(key);
KeyValuePair? oldItem;
if (base.FindItem(ref searchKey, out oldItem))
return oldItem.Value.Value;
KeyValuePair? newItem = new KeyValuePair(key, valueFactory(key));
base.GetOldestItem(ref newItem, out oldItem);
return oldItem.Value.Value;
}
}
}