wasSharp

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 45  →  ?path2? @ 54
File deleted
/Collections/Specialized/ObservableHashSet.cs
File deleted
/Collections/Specialized/MultiKeyDictionary.cs
/Collections/Specialized/ConcurrentList.cs
@@ -53,7 +53,7 @@
}
}
 
public int Count
public bool IsReadOnly
{
get
{
@@ -60,32 +60,31 @@
_lock.EnterReadLock();
try
{
return _list.Count;
return ((IList<T>)_list).IsReadOnly;
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public bool IsReadOnly
}
 
int ICollection<T>.Count
{
get
get
{
_lock.EnterReadLock();
try
{
return ((IList<T>)_list).IsReadOnly;
_lock.EnterReadLock();
try
{
return _list.Count;
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
}
 
public T this[int index]
{
get
/Collections/Specialized/ConcurrentMultiKeyDictionary.cs
@@ -0,0 +1,636 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
// Based on the work of Herman Schoenfeld
 
using System.Collections.Generic;
using System.Linq;
using System.Threading;
 
namespace wasSharp.Collections.Specialized
{
public class ConcurrentMultiKeyDictionary<K1, K2, V> : Dictionary<K1, Dictionary<K2, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[K1 key1, K2 key2]
{
get
{
_lock.EnterReadLock();
try
{
if (!ContainsKey(key1) || !this[key1].ContainsKey(key2))
{
return default(V);
}
return base[key1][key2];
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new Dictionary<K2, V>();
 
this[key1][key2] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public new IEnumerable<V> Values
{
get
{
_lock.EnterReadLock();
try
{
return base.Values.SelectMany(baseDict => baseDict.Keys, (baseDict, baseKey) => baseDict[baseKey]);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public void Add(K1 key1, K2 key2, V value)
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new Dictionary<K2, V>();
 
this[key1][key2] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
 
public void Remove(K1 key1, K2 key2)
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1) || !this[key1].ContainsKey(key2))
{
return;
}
this[key1].Remove(key2);
Remove(key1);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
 
public bool ContainsKey(K1 key1, K2 key2)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) && this[key1].ContainsKey(key2);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public bool TryGetValue(K1 key1, K2 key2, out V value)
{
_lock.EnterReadLock();
try
{
if (!ContainsKey(key1) || !this[key1].ContainsKey(key2))
{
value = default(V);
return false;
}
value = base[key1][key2];
return true;
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public class MultiKeyDictionary<K1, K2, K3, V> : Dictionary<K1, ConcurrentMultiKeyDictionary<K2, K3, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[K1 key1, K2 key2, K3 key3]
{
get
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) ? this[key1][key2, key3] : default(V);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new ConcurrentMultiKeyDictionary<K2, K3, V>();
 
this[key1][key2, key3] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public bool ContainsKey(K1 key1, K2 key2, K3 key3)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) && this[key1].ContainsKey(key2, key3);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public void Add(K1 key1, K2 key2, K3 key3, V value)
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new ConcurrentMultiKeyDictionary<K2, K3, V>();
this[key1][key2, key3] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
 
public void Remove(K1 key1, K2 key2, K3 key3)
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1) || !this[key1].ContainsKey(key2, key3))
{
return;
}
this[key1][key2].Remove(key3);
this[key1].Remove(key2);
Remove(key1);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
 
public bool TryGetValue(K1 key1, K2 key2, K3 key3, out V value)
{
_lock.EnterReadLock();
try
{
if (!ContainsKey(key1) || !this[key1].ContainsKey(key2, key3))
{
value = default(V);
return false;
}
value = base[key1][key2, key3];
return true;
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public class MultiKeyDictionary<K1, K2, K3, K4, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[K1 key1, K2 key2, K3 key3, K4 key4]
{
get
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) ? this[key1][key2, key3, key4] : default(V);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new MultiKeyDictionary<K2, K3, K4, V>();
this[key1][key2, key3, key4] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public class MultiKeyDictionary<K1, K2, K3, K4, K5, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5]
{
get
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) ? this[key1][key2, key3, key4, key5] : default(V);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, V>();
this[key1][key2, key3, key4, key5] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, V> :
Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6]
{
get
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6] : default(V);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, V>();
this[key1][key2, key3, key4, key5, key6] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, V> :
Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7]
{
get
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7] : default(V);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, V>();
this[key1][key2, key3, key4, key5, key6, key7] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, V> :
Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8]
{
get
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8] : default(V);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, V>();
this[key1][key2, key3, key4, key5, key6, key7, key8] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, K9, V> :
Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9]
{
get
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8, key9] : default(V);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, V>();
this[key1][key2, key3, key4, key5, key6, key7, key8, key9] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8, key9);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, V> :
Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10]
{
get
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1)
? this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10]
: default(V);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, V>();
this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9,
K10 key10)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) &&
this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8, key9, key10);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, V> :
Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, V>>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
 
public V this[
K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10, K11 key11]
{
get
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1)
? this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10, key11]
: default(V);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
set
{
_lock.EnterWriteLock();
try
{
if (!ContainsKey(key1))
this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, V>();
this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10, key11] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9,
K10 key10, K11 key11)
{
_lock.EnterReadLock();
try
{
return ContainsKey(key1) &&
this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8, key9, key10, key11);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
}
/Collections/Specialized/ConcurrentQueueChangedEventHandler.cs
@@ -0,0 +1,27 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ObservableConcurrentQueue.cs" company="BledSoft">
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
// To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
// </copyright>
// <Author>
// Cheikh Younes
// </Author>
// --------------------------------------------------------------------------------------------------------------------
namespace wasSharp.Collections.Specialized
{
/// <summary>
/// Observable Concurrent queue changed event handler
/// </summary>
/// <typeparam name="T">
/// The concurrent queue elements type
/// </typeparam>
/// <param name="sender">
/// The sender.
/// </param>
/// <param name="args">
/// The <see cref="NotifyConcurrentQueueChangedEventArgs{T}"/> instance containing the event data.
/// </param>
public delegate void ConcurrentQueueChangedEventHandler<T>(
object sender,
NotifyConcurrentQueueChangedEventArgs<T> args);
}
/Collections/Specialized/NotifyConcurrentQueueChangedAction.cs
@@ -0,0 +1,37 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ObservableConcurrentQueue.cs" company="BledSoft">
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
// To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
// </copyright>
// <Author>
// Cheikh Younes
// </Author>
// --------------------------------------------------------------------------------------------------------------------
namespace wasSharp.Collections.Specialized
{
/// <summary>
/// The notify concurrent queue changed action.
/// </summary>
public enum NotifyConcurrentQueueChangedAction
{
/// <summary>
/// The enqueue
/// </summary>
Enqueue,
 
/// <summary>
/// The de-queue
/// </summary>
Dequeue,
 
/// <summary>
/// The peek
/// </summary>
Peek,
 
/// <summary>
/// The empty
/// </summary>
Empty
}
}
/Collections/Specialized/NotifyConcurrentQueueChangedEventArgs.cs
@@ -0,0 +1,72 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ObservableConcurrentQueue.cs" company="BledSoft">
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
// To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
// </copyright>
// <Author>
// Cheikh Younes
// </Author>
// --------------------------------------------------------------------------------------------------------------------
using System;
 
namespace wasSharp.Collections.Specialized
{
/// <summary>
/// The notify concurrent queue changed event args.
/// </summary>
/// <typeparam name="T">
/// The item type
/// </typeparam>
public class NotifyConcurrentQueueChangedEventArgs<T> : EventArgs
{
#region Constructors and Destructors
 
/// <summary>
/// Initializes a new instance of the <see cref="NotifyConcurrentQueueChangedEventArgs{T}"/> class.
/// </summary>
/// <param name="action">
/// The action.
/// </param>
/// <param name="changedItem">
/// The changed item.
/// </param>
public NotifyConcurrentQueueChangedEventArgs(NotifyConcurrentQueueChangedAction action, T changedItem)
{
this.Action = action;
this.ChangedItem = changedItem;
}
 
/// <summary>
/// Initializes a new instance of the <see cref="NotifyConcurrentQueueChangedEventArgs{T}"/> class.
/// </summary>
/// <param name="action">
/// The action.
/// </param>
public NotifyConcurrentQueueChangedEventArgs(NotifyConcurrentQueueChangedAction action)
{
this.Action = action;
}
 
#endregion
 
#region Public Properties
 
/// <summary>
/// Gets the action.
/// </summary>
/// <value>
/// The action.
/// </value>
public NotifyConcurrentQueueChangedAction Action { get; private set; }
 
/// <summary>
/// Gets the changed item.
/// </summary>
/// <value>
/// The changed item.
/// </value>
public T ChangedItem { get; private set; }
 
#endregion
}
}
/Collections/Specialized/ObservableConcurrentHashSet.cs
@@ -0,0 +1,272 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
 
namespace wasSharp.Collections.Specialized
{
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// An implementation of an observable HashSet.
/// </summary>
/// <typeparam name="T">the object type</typeparam>
public class ObservableConcurrentHashSet<T> : ICollection<T>, INotifyCollectionChanged, IEnumerable<T>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly HashSet<T> store = new HashSet<T>();
 
public ObservableConcurrentHashSet(HashSet<T> set)
{
_lock.EnterWriteLock();
try
{
UnionWith(set);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
 
public ObservableConcurrentHashSet()
{
}
 
public ObservableConcurrentHashSet(T item)
{
_lock.EnterWriteLock();
try
{
Add(item);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
 
public ObservableConcurrentHashSet(ObservableConcurrentHashSet<T> other)
{
_lock.EnterWriteLock();
try
{
UnionWith(other);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
 
public ObservableConcurrentHashSet(IEnumerable<T> list)
{
_lock.EnterWriteLock();
try
{
UnionWith(list);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
 
public bool IsVirgin { get; private set; } = true;
 
public IEnumerator<T> GetEnumerator()
{
_lock.EnterReadLock();
try
{
using (var enumerator = store.GetEnumerator())
{
while (enumerator.MoveNext())
yield return enumerator.Current;
}
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
IEnumerator IEnumerable.GetEnumerator()
{
_lock.EnterReadLock();
try
{
using (var enumerator = store.GetEnumerator())
{
while (enumerator.MoveNext())
yield return enumerator.Current;
}
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public void Add(T item)
{
_lock.EnterWriteLock();
try
{
store.Add(item);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
IsVirgin = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
 
public void Clear()
{
_lock.EnterWriteLock();
try
{
store.Clear();
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
if (!IsVirgin)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
IsVirgin = false;
}
 
public bool Contains(T item)
{
_lock.EnterReadLock();
try
{
return store.Contains(item);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public void CopyTo(T[] array, int arrayIndex)
{
_lock.EnterReadLock();
try
{
store.CopyTo(array, arrayIndex);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public bool Remove(T item)
{
_lock.EnterWriteLock();
bool removed;
try
{
removed = store.Remove(item);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
IsVirgin = false;
if (removed)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
return removed;
}
 
public int Count => store.Count();
 
public bool IsReadOnly => false;
 
public event NotifyCollectionChangedEventHandler CollectionChanged;
 
public void UnionWith(IEnumerable<T> list)
{
var added = new List<T>(list.Except(store));
_lock.EnterWriteLock();
try
{
store.UnionWith(added);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
if (!IsVirgin && added.Any())
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added));
IsVirgin = false;
}
 
private void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);
}
 
public void ExceptWith(IEnumerable<T> list)
{
var removed = new List<T>(list.Intersect(store));
_lock.EnterWriteLock();
try
{
store.ExceptWith(removed);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
if (!IsVirgin && removed.Any())
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
removed));
IsVirgin = false;
}
 
public void RemoveWhere(Func<T, bool> func)
{
var removed = new List<T>(store.Where(func));
_lock.EnterWriteLock();
try
{
store.ExceptWith(removed);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
if (!IsVirgin && removed.Any())
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
removed));
IsVirgin = false;
}
 
public IEnumerable<T> AsEnumerable()
{
_lock.EnterWriteLock();
try
{
return store.AsEnumerable();
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
}
/Collections/Specialized/ObservableConcurrentQueue.cs
@@ -0,0 +1,131 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ObservableConcurrentQueue.cs" company="BledSoft">
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
// To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
// </copyright>
// <Author>
// Cheikh Younes
// </Author>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Concurrent;
using System.Threading.Tasks;
 
namespace wasSharp.Collections.Specialized
{
/// <summary>
/// The observable concurrent queue.
/// </summary>
/// <typeparam name="T">
/// The content type
/// </typeparam>
public sealed class ObservableConcurrentQueue<T> : ConcurrentQueue<T>
{
#region Public Events
 
/// <summary>
/// Occurs when concurrent queue elements [changed].
/// </summary>
public event ConcurrentQueueChangedEventHandler<T> ContentChanged;
 
#endregion
 
#region Public Methods and Operators
 
/// <summary>
/// Adds an object to the end of the <see cref="T:System.Collections.Concurrent.ConcurrentQueue`1"/>.
/// </summary>
/// <param name="item">
/// The object to add to the end of the <see cref="T:System.Collections.Concurrent.ConcurrentQueue`1"/>
/// . The value can be a null reference (Nothing in Visual Basic) for reference types.
/// </param>
public new async Task Enqueue(T item)
{
await new Task(() =>
{
base.Enqueue(item);
 
// Raise event added event
this.OnContentChanged(
new NotifyConcurrentQueueChangedEventArgs<T>(NotifyConcurrentQueueChangedAction.Enqueue, item));
});
}
 
/// <summary>
/// Attempts to remove and return the object at the beginning of the
/// <see cref="T:System.Collections.Concurrent.ConcurrentQueue`1"/>.
/// </summary>
/// <param name="result">
/// When this method returns, if the operation was successful, <paramref name="result"/> contains the
/// object removed. If no object was available to be removed, the value is unspecified.
/// </param>
/// <returns>
/// true if an element was removed and returned from the beginning of the
/// <see cref="T:System.Collections.Concurrent.ConcurrentQueue`1"/> successfully; otherwise, false.
/// </returns>
public new bool TryDequeue(out T result)
{
if (!base.TryDequeue(out result))
{
return false;
}
 
// Raise item dequeued event
this.OnContentChanged(
new NotifyConcurrentQueueChangedEventArgs<T>(NotifyConcurrentQueueChangedAction.Dequeue, result));
 
if (this.IsEmpty)
{
// Raise Queue empty event
this.OnContentChanged(
new NotifyConcurrentQueueChangedEventArgs<T>(NotifyConcurrentQueueChangedAction.Empty));
}
 
return true;
}
 
/// <summary>
/// Attempts to return an object from the beginning of the
/// <see cref="T:System.Collections.Concurrent.ConcurrentQueue`1"/> without removing it.
/// </summary>
/// <param name="result">
/// When this method returns, <paramref name="result"/> contains an object from the beginning of the
/// <see cref="T:System.Collections.Concurrent.ConcurrentQueue`1"/> or an unspecified value if the operation failed.
/// </param>
/// <returns>
/// true if and object was returned successfully; otherwise, false.
/// </returns>
public new bool TryPeek(out T result)
{
var retValue = base.TryPeek(out result);
if (retValue)
{
// Raise item dequeued event
this.OnContentChanged(
new NotifyConcurrentQueueChangedEventArgs<T>(NotifyConcurrentQueueChangedAction.Peek, result));
}
 
return retValue;
}
 
#endregion
 
#region Methods
 
/// <summary>
/// Raises the <see cref="E:Changed"/> event.
/// </summary>
/// <param name="args">
/// The <see cref="NotifyConcurrentQueueChangedEventArgs{T}"/> instance containing the event data.
/// </param>
private void OnContentChanged(NotifyConcurrentQueueChangedEventArgs<T> args)
{
var handler = this.ContentChanged;
if (handler != null)
{
handler(this, args);
}
}
 
#endregion
}
}
/Collections/Specialized/ObservableDictionary.cs
@@ -1,274 +1,258 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Threading;
 
namespace wasSharp.Collections.Specialized
{
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// An implementation of an observable Dictionary.
/// </summary>
/// <typeparam name="K">the key type</typeparam>
/// <typeparam name="V">the value type</typeparam>
public class ObservableDictionary<K, V> : IDictionary<K, V>, INotifyCollectionChanged
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly Dictionary<K, V> store = new Dictionary<K, V>();
 
public bool IsVirgin { get; private set; } = true;
 
public V this[K key]
{
get
{
_lock.EnterReadLock();
try
{
return store[key];
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
set
{
_lock.EnterWriteLock();
try
{
store[key] = value;
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
}
 
public int Count => store.Count;
 
public bool IsReadOnly => false;
 
public ICollection<K> Keys
{
get
{
_lock.EnterReadLock();
try
{
return store.Keys;
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public ICollection<V> Values
{
get
{
_lock.EnterReadLock();
try
{
return store.Values;
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
 
public void Add(KeyValuePair<K, V> item)
{
_lock.EnterWriteLock();
try
{
((IDictionary<K, V>) store).Add(item);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
IsVirgin = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
 
public void Add(K key, V value)
{
_lock.EnterWriteLock();
try
{
store.Add(key, value);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
IsVirgin = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,
new KeyValuePair<K, V>(key, value)));
}
 
public void Clear()
{
_lock.EnterWriteLock();
try
{
store.Clear();
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
if (!IsVirgin)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
IsVirgin = false;
}
 
public bool Contains(KeyValuePair<K, V> item)
{
_lock.EnterReadLock();
try
{
return ((IDictionary<K, V>) store).Contains(item);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public bool ContainsKey(K key)
{
_lock.EnterReadLock();
try
{
return store.ContainsKey(key);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
{
_lock.EnterReadLock();
try
{
((IDictionary<K, V>) store).CopyTo(array, arrayIndex);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
_lock.EnterReadLock();
try
{
using (var enumerator = ((IDictionary<K, V>)store).GetEnumerator())
{
while (enumerator.MoveNext())
yield return enumerator.Current;
}
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public bool Remove(KeyValuePair<K, V> item)
{
_lock.EnterWriteLock();
bool removed;
try
{
removed = ((IDictionary<K, V>) store).Remove(item);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
IsVirgin = false;
if (removed)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
return removed;
}
 
public bool Remove(K key)
{
KeyValuePair<K, V> item;
_lock.EnterWriteLock();
bool removed;
try
{
if (store.ContainsKey(key))
item = new KeyValuePair<K, V>(key, store[key]);
 
removed = store.Remove(key);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
IsVirgin = false;
if (removed)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
return removed;
}
 
public bool TryGetValue(K key, out V value)
{
_lock.EnterReadLock();
try
{
return store.TryGetValue(key, out value);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
IEnumerator IEnumerable.GetEnumerator()
{
_lock.EnterReadLock();
try
{
using (var enumerator = ((IDictionary<K, V>)store).GetEnumerator())
{
while (enumerator.MoveNext())
yield return enumerator.Current;
}
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
 
public event NotifyCollectionChangedEventHandler CollectionChanged;
 
private void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);
}
}
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
/// From https://gist.github.com/kzu/cfe3cb6e4fe3efea6d24
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace wasSharp.Collections.Specialized
{
/// <summary>
/// Provides a dictionary for use with data binding.
/// </summary>
/// <typeparam name="TKey">Specifies the type of the keys in this collection.</typeparam>
/// <typeparam name="TValue">Specifies the type of the values in this collection.</typeparam>
[DebuggerDisplay("Count={Count}")]
public class ObservableDictionary<TKey, TValue> :
ICollection<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>,
INotifyCollectionChanged, INotifyPropertyChanged
{
readonly IDictionary<TKey, TValue> dictionary;
 
/// <summary>Event raised when the collection changes.</summary>
public event NotifyCollectionChangedEventHandler CollectionChanged = (sender, args) => { };
 
/// <summary>Event raised when a property on the collection changes.</summary>
public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { };
 
/// <summary>
/// Initializes an instance of the class.
/// </summary>
public ObservableDictionary()
: this(new Dictionary<TKey, TValue>())
{
}
 
/// <summary>
/// Initializes an instance of the class using another dictionary as
/// the key/value store.
/// </summary>
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
this.dictionary = dictionary;
}
 
void AddWithNotification(KeyValuePair<TKey, TValue> item)
{
AddWithNotification(item.Key, item.Value);
}
 
void AddWithNotification(TKey key, TValue value)
{
dictionary.Add(key, value);
 
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,
new KeyValuePair<TKey, TValue>(key, value)));
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs("Keys"));
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
}
 
bool RemoveWithNotification(TKey key)
{
TValue value;
if (dictionary.TryGetValue(key, out value) && dictionary.Remove(key))
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
new KeyValuePair<TKey, TValue>(key, value)));
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs("Keys"));
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
 
return true;
}
 
return false;
}
 
public void Clear()
{
dictionary.Clear();
 
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs("Keys"));
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
}
 
void UpdateWithNotification(TKey key, TValue value)
{
TValue existing;
if (dictionary.TryGetValue(key, out existing))
{
dictionary[key] = value;
 
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
new KeyValuePair<TKey, TValue>(key, value),
new KeyValuePair<TKey, TValue>(key, existing)));
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
}
else
{
AddWithNotification(key, value);
}
}
 
/// <summary>
/// Allows derived classes to raise custom property changed events.
/// </summary>
protected void RaisePropertyChanged(PropertyChangedEventArgs args)
{
PropertyChanged(this, args);
}
 
#region IDictionary<TKey,TValue> Members
 
/// <summary>
/// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2" />.
/// </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>
public void Add(TKey key, TValue value)
{
AddWithNotification(key, value);
}
 
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the specified key.
/// </summary>
/// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2" />.</param>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the key; otherwise, false.
/// </returns>
public bool ContainsKey(TKey key)
{
return dictionary.ContainsKey(key);
}
 
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2" />.
/// </summary>
/// <returns>An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2" />.</returns>
public ICollection<TKey> Keys
{
get { return dictionary.Keys; }
}
 
/// <summary>
/// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2" />.
/// </summary>
/// <param name="key">The key of the element to remove.</param>
/// <returns>
/// true if the element is successfully removed; otherwise, false. This method also returns false if <paramref name="key" /> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2" />.
/// </returns>
public bool Remove(TKey key)
{
return RemoveWithNotification(key);
}
 
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">The key whose value to get.</param>
/// <param name="value">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 <paramref name="value" /> parameter. This parameter is passed uninitialized.</param>
/// <returns>
/// true if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the specified key; otherwise, false.
/// </returns>
public bool TryGetValue(TKey key, out TValue value)
{
return dictionary.TryGetValue(key, out value);
}
 
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2" />.
/// </summary>
/// <returns>An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2" />.</returns>
public ICollection<TValue> Values
{
get { return dictionary.Values; }
}
 
/// <summary>
/// Gets or sets the element with the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
public TValue this[TKey key]
{
get { return dictionary[key]; }
set { UpdateWithNotification(key, value); }
}
 
#endregion
 
#region ICollection<KeyValuePair<TKey,TValue>> Members
 
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
AddWithNotification(item);
}
 
void ICollection<KeyValuePair<TKey, TValue>>.Clear()
{
dictionary.Clear();
 
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs("Keys"));
PropertyChanged(this, new PropertyChangedEventArgs("Values"));
}
 
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) =>
dictionary.Contains(item);
 
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
dictionary.CopyTo(array, arrayIndex);
}
 
int ICollection<KeyValuePair<TKey, TValue>>.Count =>
dictionary.Count();
 
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly =>
dictionary.IsReadOnly;
 
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) =>
RemoveWithNotification(item.Key);
 
#endregion
 
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
 
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() =>
dictionary.GetEnumerator();
 
IEnumerator IEnumerable.GetEnumerator() =>
dictionary.GetEnumerator();
 
#endregion
 
#region Extensions
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return dictionary.GetEnumerator();
}
#endregion
}
}
/Collections/Utilities/CollectionExtensions.cs
@@ -26,8 +26,8 @@
IDictionary<TKey, TValue> otherDictionary)
{
return
(dictionary ?? new Dictionary<TKey, TValue>()).Count.Equals(
(otherDictionary ?? new Dictionary<TKey, TValue>()).Count) &&
(dictionary ?? new Dictionary<TKey, TValue>()).Count().Equals(
(otherDictionary ?? new Dictionary<TKey, TValue>()).Count()) &&
(otherDictionary ?? new Dictionary<TKey, TValue>())
.OrderBy(kvp => kvp.Key)
.SequenceEqual((dictionary ?? new Dictionary<TKey, TValue>())
@@ -59,10 +59,10 @@
}
}
 
public static MultiKeyDictionary<K1, K2, V> ToMultiKeyDictionary<S, K1, K2, V>(this IEnumerable<S> items,
public static ConcurrentMultiKeyDictionary<K1, K2, V> ToMultiKeyDictionary<S, K1, K2, V>(this IEnumerable<S> items,
Func<S, K1> key1, Func<S, K2> key2, Func<S, V> value)
{
var dict = new MultiKeyDictionary<K1, K2, V>();
var dict = new ConcurrentMultiKeyDictionary<K1, K2, V>();
foreach (var i in items)
{
dict.Add(key1(i), key2(i), value(i));