Get list of active items from ConditionalWeakTable<T> - c#

The .NET 4.0 ConditionalWeakTable<T> is effectively a dictionary where the dictionary's keys are weak referenced and can be collected, which is exactly what I need. The problem is that I need to be able to get all live keys from this dictionary, but MSDN states:
It does not include all the methods (such as GetEnumerator or
Contains) that a dictionary typically has.
Is there a possibility to retrieve the live keys or key-value pairs from a ConditionalWeakTable<T>?

I ended up creating my own wrapper:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
public sealed class ConditionalHashSet<T> where T : class
{
private readonly object locker = new object();
private readonly List<WeakReference> weakList = new List<WeakReference>();
private readonly ConditionalWeakTable<T, WeakReference> weakDictionary =
new ConditionalWeakTable<T, WeakReference>();
public void Add(T item)
{
lock (this.locker)
{
var reference = new WeakReference(item);
this.weakDictionary.Add(item, reference);
this.weakList.Add(reference);
this.Shrink();
}
}
public void Remove(T item)
{
lock (this.locker)
{
WeakReference reference;
if (this.weakDictionary.TryGetValue(item, out reference))
{
reference.Target = null;
this.weakDictionary.Remove(item);
}
}
}
public T[] ToArray()
{
lock (this.locker)
{
return (
from weakReference in this.weakList
let item = (T)weakReference.Target
where item != null
select item)
.ToArray();
}
}
private void Shrink()
{
// This method prevents the List<T> from growing indefinitely, but
// might also cause a performance problem in some cases.
if (this.weakList.Capacity == this.weakList.Count)
{
this.weakList.RemoveAll(weak => !weak.IsAlive);
}
}
}

In some recent framework version, the ConditionalWeakTable<TKey,TValue> now implements IEnumerator interface. Check out Microsoft Docs.
This applies to
.NET Core >= 2.0
.NET Standard >= 2.1
This is not solving the problem if someone is stuck with .NET Framework. Otherwise, this may help if, like me, it's only a matter of updating from .NET Standard 2.0 to 2.1.

This will work without the performance problems.
The key to the problem is to use a "holder" object as a value in the ConditionalWeakTable, so that when the key gets dropped, the holder's finalizer will trigger, which removes the key from the "active list" of keys.
I tested this and it works.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Util
{
public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDisposable
where TKey : class
where TValue : class
{
private readonly object locker = new object();
//private readonly HashSet<WeakReference> weakKeySet = new HashSet<WeakReference>(new ObjectReferenceEqualityComparer<WeakReference>());
private ConditionalWeakTable<TKey, WeakKeyHolder> keyHolderMap = new ConditionalWeakTable<TKey, WeakKeyHolder>();
private Dictionary<WeakReference, TValue> valueMap = new Dictionary<WeakReference, TValue>(new ObjectReferenceEqualityComparer<WeakReference>());
private class WeakKeyHolder
{
private WeakDictionary<TKey, TValue> outer;
private WeakReference keyRef;
public WeakKeyHolder(WeakDictionary<TKey, TValue> outer, TKey key)
{
this.outer = outer;
this.WeakRef = new WeakReference(key);
}
public WeakReference WeakRef { get; private set; }
~WeakKeyHolder()
{
this.outer?.onKeyDrop(this.WeakRef); // Nullable operator used just in case this.outer gets set to null by GC before this finalizer runs. But I haven't had this happen.
}
}
private void onKeyDrop(WeakReference weakKeyRef)
{
lock(this.locker)
{
if (!this.bAlive)
return;
//this.weakKeySet.Remove(weakKeyRef);
this.valueMap.Remove(weakKeyRef);
}
}
// The reason for this is in case (for some reason which I have never seen) the finalizer trigger doesn't work
// There is not much performance penalty with this, since this is only called in cases when we would be enumerating the inner collections anyway.
private void manualShrink()
{
var keysToRemove = this.valueMap.Keys.Where(k => !k.IsAlive).ToList();
foreach (var key in keysToRemove)
valueMap.Remove(key);
}
private Dictionary<TKey, TValue> currentDictionary
{
get
{
lock(this.locker)
{
this.manualShrink();
return this.valueMap.ToDictionary(p => (TKey) p.Key.Target, p => p.Value);
}
}
}
public TValue this[TKey key]
{
get
{
if (this.TryGetValue(key, out var val))
return val;
throw new KeyNotFoundException();
}
set
{
this.set(key, value, isUpdateOkay: true);
}
}
private bool set(TKey key, TValue val, bool isUpdateOkay)
{
lock (this.locker)
{
if (this.keyHolderMap.TryGetValue(key, out var weakKeyHolder))
{
if (!isUpdateOkay)
return false;
this.valueMap[weakKeyHolder.WeakRef] = val;
return true;
}
weakKeyHolder = new WeakKeyHolder(this, key);
this.keyHolderMap.Add(key, weakKeyHolder);
//this.weakKeySet.Add(weakKeyHolder.WeakRef);
this.valueMap.Add(weakKeyHolder.WeakRef, val);
return true;
}
}
public ICollection<TKey> Keys
{
get
{
lock(this.locker)
{
this.manualShrink();
return this.valueMap.Keys.Select(k => (TKey) k.Target).ToList();
}
}
}
public ICollection<TValue> Values
{
get
{
lock (this.locker)
{
this.manualShrink();
return this.valueMap.Select(p => p.Value).ToList();
}
}
}
public int Count
{
get
{
lock (this.locker)
{
this.manualShrink();
return this.valueMap.Count;
}
}
}
public bool IsReadOnly => false;
public void Add(TKey key, TValue value)
{
if (!this.set(key, value, isUpdateOkay: false))
throw new ArgumentException("Key already exists");
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this.Add(item.Key, item.Value);
}
public void Clear()
{
lock(this.locker)
{
this.keyHolderMap = new ConditionalWeakTable<TKey, WeakKeyHolder>();
this.valueMap.Clear();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
WeakKeyHolder weakKeyHolder = null;
object curVal = null;
lock (this.locker)
{
if (!this.keyHolderMap.TryGetValue(item.Key, out weakKeyHolder))
return false;
curVal = weakKeyHolder.WeakRef.Target;
}
return (curVal?.Equals(item.Value) == true);
}
public bool ContainsKey(TKey key)
{
lock (this.locker)
{
return this.keyHolderMap.TryGetValue(key, out var weakKeyHolder);
}
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
((IDictionary<TKey, TValue>) this.currentDictionary).CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return this.currentDictionary.GetEnumerator();
}
public bool Remove(TKey key)
{
lock (this.locker)
{
if (!this.keyHolderMap.TryGetValue(key, out var weakKeyHolder))
return false;
this.keyHolderMap.Remove(key);
this.valueMap.Remove(weakKeyHolder.WeakRef);
return true;
}
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
lock (this.locker)
{
if (!this.keyHolderMap.TryGetValue(item.Key, out var weakKeyHolder))
return false;
if (weakKeyHolder.WeakRef.Target?.Equals(item.Value) != true)
return false;
this.keyHolderMap.Remove(item.Key);
this.valueMap.Remove(weakKeyHolder.WeakRef);
return true;
}
}
public bool TryGetValue(TKey key, out TValue value)
{
lock (this.locker)
{
if (!this.keyHolderMap.TryGetValue(key, out var weakKeyHolder))
{
value = default(TValue);
return false;
}
value = this.valueMap[weakKeyHolder.WeakRef];
return true;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private bool bAlive = true;
public void Dispose()
{
this.Dispose(true);
}
protected void Dispose(bool bManual)
{
if (bManual)
{
Monitor.Enter(this.locker);
if (!this.bAlive)
return;
}
try
{
this.keyHolderMap = null;
this.valueMap = null;
this.bAlive = false;
}
finally
{
if (bManual)
Monitor.Exit(this.locker);
}
}
~WeakDictionary()
{
this.Dispose(false);
}
}
public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T>
{
public static ObjectReferenceEqualityComparer<T> Default = new ObjectReferenceEqualityComparer<T>();
public bool Equals(T x, T y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(T obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
public class ObjectReferenceEqualityComparer : ObjectReferenceEqualityComparer<object>
{
}
}

Related

C# ConcurrentDictionary conditional Add

I am trying to use ConcurrentDictionary to implement a cap limited cache. When the cache reaches its capacity, further additions of new items are rejected. The code snippet is as follows:
var result = this.cache.AddOrUpdate(
key,
(key1) =>
{
if (!this.IsFull())
{
return new List<MyObject> { value };
}
what to return here??
},
(key1, value1) =>
{
value1.Add(value);
return value1;
});
My question here if the cache is full what should I return here? null? or what?
You have two options:
Write a new concurrrent data struct to manage your cache constraints. This is harder but if such a struct is going to be used in several places within your solution thet go for this one.
Delegate cache's constraints management to the class that actually holds the cache. This is easier.
#Kirill Polishchuck answer is an example of the second alternative, but I belive it is not threadsafe so I would change it like so it ends up like:
if (!this.IsFull())
{
// your logic here
this.cache.AddOrUpdate(key, k=> new List<MyObject> { value };);
}
else
{
// logic
}
For the first alternative here is a sample of what you could do to implement it. We implement IDictonary<TKey,TValue> interface and we lock on those operations that require so. Also, the class does not allow further insertions if _maxCount has been surpassed:
public class MaxCountDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly Dictionary<TKey, TValue> _dictionary;
private readonly object _lock;
public MaxCountDictionary(int maxCount) : this(maxCount, EqualityComparer<TKey>.Default) { }
public MaxCountDictionary(int maxCount, IEqualityComparer<TKey> equalityComparer)
{
_lock = new object();
MaxCount = maxCount;
_dictionary = new Dictionary<TKey, TValue>(equalityComparer);
}
public int MaxCount { get; }
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _dictionary.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
public void Clear()
{
lock (_lock)
{
_dictionary.Clear();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
lock (_lock)
{
return ((IDictionary<TKey, TValue>) _dictionary).Contains(item);
}
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
lock (_lock)
{
((IDictionary<TKey, TValue>) _dictionary).CopyTo(array, arrayIndex);
}
}
public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key);
public int Count
{
get
{
lock (_lock)
{
return _dictionary.Count;
}
}
}
public bool IsReadOnly => ((IDictionary<TKey, TValue>) _dictionary).IsReadOnly;
public bool ContainsKey(TKey key)
{
lock (_lock)
{
return _dictionary.ContainsKey(key);
}
}
public void Add(TKey key, TValue value)
{
lock (_lock)
{
if (_dictionary.Count < MaxCount) _dictionary.Add(key, value);
}
}
public bool Remove(TKey key)
{
lock (_lock)
{
return _dictionary.Remove(key);
}
}
public bool TryGetValue(TKey key, out TValue value)
{
lock (_lock)
{
return _dictionary.TryGetValue(key, out value);
}
}
public TValue this[TKey key]
{
get
{
lock (_lock)
{
return _dictionary[key];
}
}
set
{
lock (_lock)
{
if (_dictionary.ContainsKey(key) || _dictionary.Count < MaxCount) _dictionary[key] = value;
}
}
}
public ICollection<TKey> Keys
{
get
{
lock (_lock)
{
return _dictionary.Keys.ToArray();
}
}
}
public ICollection<TValue> Values
{
get
{
lock (_lock)
{
return _dictionary.Values.ToArray();
}
}
}
public void AddOrUpdate(TKey key, TValue value, Func<TKey, TValue, TValue> updateValueFactory)
{
lock (_lock)
{
if (_dictionary.ContainsKey(key))
_dictionary[key] = updateValueFactory(key, value);
else if (_dictionary.Count < MaxCount) _dictionary[key] = value;
}
}
}
With this other alternative you simply pass a MaxCount parameter to your collection, you can change it to accept a Func<bool> so that the dictionary can figure out whether to add more items or not, this way you can pass your IsFull method to it.
Beware: This is demo code, it is just a sample, it is not thread safe to enumarate, adjust it to your needs.

C# HashSet<T> read-only workaround

Here is this sample code:
static class Store
{
private static List<String> strList = new List<string>();
private static HashSet<String> strHashSet = new HashSet<string>();
public static List<String> NormalList
{
get { return strList; }
}
public static HashSet<String> NormalHashSet
{
get { return strHashSet; }
}
public static IReadOnlyList<String> ReadonlyList
{
get { return (IReadOnlyList<String>)strList; }
}
public static IReadOnlyCollection<String> ReadonlyHashSet
{
get { return (IReadOnlyCollection<String>)strHashSet; }
}
public static IReadOnlyList<String> Real_ReadonlyList
{
get { return (IReadOnlyList<String>)strList.AsReadOnly(); }
}
public static IReadOnlyCollection<String> Real_ReadonlyHashSet
{
get
{
List<String> tmpList = new List<String>(strHashSet);
return (IReadOnlyList<String>)(tmpList).AsReadOnly();
}
}
}
And here is a test code:
// normal behaviour
// you can modify the list and the hashset
Store.NormalList.Add("some string 1");
Store.NormalHashSet.Add("some string 1");
// tricky behaviour
// you can still modify the list and the hashset
((List<String>)Store.ReadonlyList).Add("some string 2");
((HashSet<String>)Store.ReadonlyHashSet).Add("some string 2");
// expected read-only behaviour
// you can NOT modify
// throws InvalidCastException
((List<String>)Store.Real_ReadonlyList).Add("some string 3");
// throws InvalidCastException
((HashSet<String>)Store.Real_ReadonlyHashSet).Add("some string 3");
My questions are these:
Is there a better solution for the "Real_ReadonlyHashSet" property?
Will Microsoft some day implement the "AsReadOnly" method to the HashSet<T>?
Here is the entirety of the code of .AsReadOnly()
public ReadOnlyCollection<T> AsReadOnly() {
Contract.Ensures(Contract.Result<ReadOnlyCollection<T>>() != null);
return new ReadOnlyCollection<T>(this);
}
The first line is not even necessary if you are not using CodeContracts. However, ReadOnlyCollection<T> only supports IList<T> which HashSet<T> does not support.
What I would do is make your own ReadOnlySet<T> class that takes in a ISet<T> and only passes through the read operations like ReadOnlyCollection<T> does internally.
UPDATE:
Here is a fully fleshed out ReadOnlySet<T> I quickly wrote up along with a extension method that adds a .AsReadOnly() on to anything that implements ISet<T>
public static class SetExtensionMethods
{
public static ReadOnlySet<T> AsReadOnly<T>(this ISet<T> set)
{
return new ReadOnlySet<T>(set);
}
}
public class ReadOnlySet<T> : IReadOnlyCollection<T>, ISet<T>
{
private readonly ISet<T> _set;
public ReadOnlySet(ISet<T> set)
{
_set = set;
}
public IEnumerator<T> GetEnumerator()
{
return _set.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) _set).GetEnumerator();
}
void ICollection<T>.Add(T item)
{
throw new NotSupportedException("Set is a read only set.");
}
public void UnionWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public void IntersectWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public void ExceptWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public void SymmetricExceptWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public bool IsSubsetOf(IEnumerable<T> other)
{
return _set.IsSubsetOf(other);
}
public bool IsSupersetOf(IEnumerable<T> other)
{
return _set.IsSupersetOf(other);
}
public bool IsProperSupersetOf(IEnumerable<T> other)
{
return _set.IsProperSupersetOf(other);
}
public bool IsProperSubsetOf(IEnumerable<T> other)
{
return _set.IsProperSubsetOf(other);
}
public bool Overlaps(IEnumerable<T> other)
{
return _set.Overlaps(other);
}
public bool SetEquals(IEnumerable<T> other)
{
return _set.SetEquals(other);
}
public bool Add(T item)
{
throw new NotSupportedException("Set is a read only set.");
}
public void Clear()
{
throw new NotSupportedException("Set is a read only set.");
}
public bool Contains(T item)
{
return _set.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_set.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
throw new NotSupportedException("Set is a read only set.");
}
public int Count
{
get { return _set.Count; }
}
public bool IsReadOnly
{
get { return true; }
}
}
Starting from .NET 5, the HashSet<T> class now implements the IReadOnlySet<T> interface. There is no built-in ReadOnlySet<T> wrapper though, analogous to the existing ReadOnlyDictionary<TKey, TValue> class for dictionaries, but implementing one is trivial:
public class ReadOnlySet<T> : IReadOnlySet<T>
{
private readonly ISet<T> _set;
public ReadOnlySet(ISet<T> set) { ArgumentNullException.ThrowIfNull(set); _set = set; }
public int Count => _set.Count;
public bool Contains(T item) => _set.Contains(item);
public bool IsProperSubsetOf(IEnumerable<T> other) => _set.IsProperSubsetOf(other);
public bool IsProperSupersetOf(IEnumerable<T> other) => _set.IsProperSupersetOf(other);
public bool IsSubsetOf(IEnumerable<T> other) => _set.IsSubsetOf(other);
public bool IsSupersetOf(IEnumerable<T> other) => _set.IsSupersetOf(other);
public bool Overlaps(IEnumerable<T> other) => _set.Overlaps(other);
public bool SetEquals(IEnumerable<T> other) => _set.SetEquals(other);
public IEnumerator<T> GetEnumerator() => _set.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
The upcoming .NET 7 will also feature a new AsReadOnly extension method for IDictionary<TKey,TValue>s, so let's make one for ISet<T>s too:
public static ReadOnlySet<T> AsReadOnly<T>(this ISet<T> set) => new ReadOnlySet<T>(set);
Usage example:
HashSet<Item> items = new();
ReadOnlySet<Item> readOnlyItems = items.AsReadOnly();
You could write your own implementation of an IReadOnlyCollection<T> that wraps an IEnumerable<T> and a count:
public sealed class ReadOnlyCollectionFromEnumerable<T>: IReadOnlyCollection<T>
{
readonly IEnumerable<T> _data;
public ReadOnlyCollectionFromEnumerable(IEnumerable<T> data, int count)
{
_data = data;
Count = count;
}
public IEnumerator<T> GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count { get; }
}
Then you declare your ReadonlyHashSet property like this:
public static IReadOnlyCollection<String> ReadonlyHashSet
{
get { return new ReadOnlyCollectionFromEnumerable<string>(strHashSet, strHashSet.Count); }
}
I think that would solve the issue.
HashSet implements the IReadOnlyCollection interface starting
with the .NET Framework 4.6; in previous versions of the .NET
Framework, the HashSet class did not implement this interface.
Read in learn.microsoft.com
In .NET Framework 4.6 release, the HashSet implements IReadOnlyCollection interface along with the ISet interface. link...
It does seem to.
You could also do this but maybe take a performance hit:
var foo = (IReadOnlyCollection<string>) mySet.toList();

Using JSON.NET with OrderedDictionary

I have an OrderedDictionary with int keys and System.Drawing.Rectangle values. JSON.NET won't serialize the OrderedDictionary...it returns an empty object. I wrote a custom converter, but I wondered if there was an easier way. Thinking that JSON.NET might use the presence of a typed enumerator as the trigger to use its built-in code for serializing and deserializing a Dictionary<TKey, TValue> I tried this:
class Program
{
static void Main(string[] args)
{
var test = new OrderedDictionary<int, Rectangle>();
test.Add(1, new Rectangle(0, 0, 50, 50));
test.Add(42, new Rectangle(1, 1, 1, 1));
string s = JsonConvert.SerializeObject(test);
var deserialized = JsonConvert.DeserializeObject<OrderedDictionary<int, Rectangle>>(s);
var someRect = deserialized[(object)1]; // someRect is null
var someOtherRect = (Rectangle)deserialized["1"]; // InvalidCastException
}
}
public class OrderedDictionary<TKey, TValue> : OrderedDictionary, IEnumerable<KeyValuePair<TKey, TValue>>
{
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
foreach (TKey key in Keys)
{
yield return new KeyValuePair<TKey, TValue>(key, (TValue)this[key]);
}
}
}
Serialization works perfectly. However, when I deserialize, the keys in the dictionary become strings and the Rectangles are JObjects that can't be cast to Rectangle. Is there something I can add to my OrderedDictionary<> class that will allow for proper deserialization with JSON.NET? Thanks.
Your problem is that, although you've added an enumerator, things like the indexer cannot be overridden. So what you're getting is the default implementation of the non-generic OrderedDictionary which doesn't give you a typed result.
So, instead of inheriting, you need a facade that fully implements the generic interface.
You'll need to verify my class (I just made the test work). I've also cheated with the Keys and Values properties (they're not often used) and some of the other ICollection methods. Just lazy :)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Drawing;
using Newtonsoft.Json;
using Xunit;
namespace XUnitTestProject1
{
public class UnitTest1
{
[Fact]
public void TestJsonRectange()
{
var test = new OrderedDictionary<int, Rectangle>();
test.Add(1, new Rectangle(0, 0, 50, 50));
test.Add(42, new Rectangle(1, 1, 1, 1));
string json = JsonConvert.SerializeObject(test);
var deserialized = JsonConvert.DeserializeObject<OrderedDictionary<int, Rectangle>>(json);
object someRect = deserialized[1];
Assert.NotNull(someRect);
Assert.True(someRect is Rectangle);
}
[Fact]
public void TestJsonString()
{
var test = new OrderedDictionary<string, string>();
test.Add("1", "11");
test.Add("42", "4242");
string json = JsonConvert.SerializeObject(test);
var deserialized = JsonConvert.DeserializeObject<OrderedDictionary<string, string>>(json);
object something = deserialized["1"];
Assert.NotNull(something);
Assert.True(something is string);
}
public class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly OrderedDictionary dic = new OrderedDictionary();
public TValue this[TKey key] { get { return (TValue)dic[key]; } set { dic[key] = value; } }
public void Add(KeyValuePair<TKey, TValue> item)
{
dic.Add(item.Key, item.Value);
}
public void Add(TKey key, TValue value)
{
dic.Add(key, value);
}
public void Clear() { dic.Clear(); }
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { }
public int Count { get { return dic.Count; } }
public bool IsReadOnly { get { return false; } }
public bool Contains(TKey key) { return dic.Contains(key); }
public bool ContainsKey(TKey key) { return dic.Contains(key); }
public bool Remove(TKey key) { dic.Remove(key); return true; }
public bool TryGetValue(TKey key, out TValue value) { value = default(TValue); return false; }
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
throw new NotImplementedException();
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { return false; }
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
foreach (DictionaryEntry entry in dic)
yield return new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private static readonly TKey[] keys = new TKey[0];
private static readonly TValue[] values = new TValue[0];
ICollection<TKey> IDictionary<TKey, TValue>.Keys { get { return keys; } }
ICollection<TValue> IDictionary<TKey, TValue>.Values { get { return values; } }
}
}
}

Thread safe SortedDictionary

I made a class that uses a SortedDictionary to store and manipulate data. The class works great except when it is implemented in a multi-threaded environment. Now, I would like to make the class thread safe by writing a wrapper class for the internal SortedDictionary class. I would like to use the Reader-Writer Locks to implement this but for now, I'm having problems just writing the wrapper class itself. Specifically, I'm not sure how to implement the Enumerator for the dictionary. Here is my complete code for the class as it stands now.
public class ConcurrentSortedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
{
#region Variables
SortedDictionary<TKey, TValue> _dict;
#endregion
#region Constructors
public ConcurrentSortedDictionary()
{
_dict = new SortedDictionary<TKey, TValue>();
}
public ConcurrentSortedDictionary(IComparer<TKey> comparer)
{
_dict = new SortedDictionary<TKey, TValue>(comparer);
}
public ConcurrentSortedDictionary(IDictionary<TKey, TValue> dictionary)
{
_dict = new SortedDictionary<TKey, TValue>(dictionary);
}
public ConcurrentSortedDictionary(IDictionary<TKey, TValue> dictionary, IComparer<TKey> comparer)
{
_dict = new SortedDictionary<TKey, TValue>(dictionary, comparer);
}
#endregion
#region Properties
public IComparer<TKey> Comparer
{
get
{
return _dict.Comparer;
}
}
public int Count
{
get
{
return _dict.Count;
}
}
public TValue this[TKey key]
{
get
{
return _dict[key];
}
set
{
_dict[key] = value;
}
}
public SortedDictionary<TKey, TValue>.KeyCollection Keys
{
get
{
return new SortedDictionary<TKey,TValue>.KeyCollection(_dict);
}
}
public SortedDictionary<TKey, TValue>.ValueCollection Values
{
get
{
return new SortedDictionary<TKey, TValue>.ValueCollection(_dict);
}
}
#endregion
#region Methods
public void Add(TKey key, TValue value)
{
_dict.Add(key, value);
}
public void Clear()
{
_dict.Clear();
}
public bool ContainsKey(TKey key)
{
return _dict.ContainsKey(key);
}
public bool ContainsValue(TValue value)
{
return _dict.ContainsValue(value);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
{
_dict.CopyTo(array, index);
}
public override bool Equals(Object obj)
{
return _dict.Equals(obj);
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dict.GetEnumerator();
}
public override int GetHashCode()
{
return _dict.GetHashCode();
}
public bool Remove(TKey key)
{
return _dict.Remove(key);
}
public override string ToString()
{
return _dict.ToString();
}
public bool TryGetValue(TKey key, out TValue value)
{
return _dict.TryGetValue(key, out value);
}
#endregion
}
When I compile the code, I get the error message:
'ConcurrentSortedDictionary' does not implement interface
member 'System.Collections.IEnumerable.GetEnumerator()'.
'ConcurrentSortedDictionary.GetEnumerator()' cannot
implement 'System.Collections.IEnumerable.GetEnumerator()' because it
does not have the matching return type of
'System.Collections.IEnumerator'.
I looked at several posts here relating to this as a reference:
How do I implement IEnumerable in my Dictionary wrapper class that implements IEnumerable<Foo>?
What's the best way of implementing a thread-safe Dictionary?
But I don't see what I'm doing wrong. Any assistance greatly appreciated.
The problem is here:
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dict.GetEnumerator();
}
You need:
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dict.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _dict.GetEnumerator();
}
The second non-generic GetEnumerator() is an explicit interface implementation and is needed as an unfortunate throwback to the days before generics and generic collections existed in C#.
See also: IEnumerable<T> provides two GetEnumerator methods - what is the difference between them? (and in particular Michael B's answer).
However if you want enumeration to be thread-safe along with the rest of your class, you may also need to write your own thread-safe IEnumerator type which cooperates with the reader/writer locks in your class!
After implementing the suggestion by dvnrrs, I now have the class working well. I even added a wrapper class for the IEnumerable interface to protect enumerations of the SortedDictionary (code modified from this example here: http://www.codeproject.com/Articles/56575/Thread-safe-enumeration-in-C). Here is the updated code with the Reader-Writer Locks included:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Threading;
public class SafeEnumerator<T> : IEnumerator<T>
{
#region Variables
// this is the (thread-unsafe)
// enumerator of the underlying collection
private readonly IEnumerator<T> _enumerator;
// this is the object we shall lock on.
private ReaderWriterLockSlim _lock;
#endregion
#region Constructor
public SafeEnumerator(IEnumerator<T> inner, ReaderWriterLockSlim readWriteLock)
{
_enumerator = inner;
_lock = readWriteLock;
// Enter lock in constructor
_lock.EnterReadLock();
}
#endregion
#region Implementation of IDisposable
public void Dispose()
{
// .. and exiting lock on Dispose()
// This will be called when the foreach loop finishes
_lock.ExitReadLock();
}
#endregion
#region Implementation of IEnumerator
// we just delegate actual implementation
// to the inner enumerator, that actually iterates
// over some collection
public bool MoveNext()
{
return _enumerator.MoveNext();
}
public void Reset()
{
_enumerator.Reset();
}
public T Current
{
get
{
return _enumerator.Current;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
#endregion
}
public class ConcurrentSortedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
{
#region Variables
private ReaderWriterLockSlim _readWriteLock = new ReaderWriterLockSlim();
SortedDictionary<TKey, TValue> _dict;
#endregion
#region Constructors
public ConcurrentSortedDictionary()
{
_dict = new SortedDictionary<TKey, TValue>();
}
public ConcurrentSortedDictionary(IComparer<TKey> comparer)
{
_dict = new SortedDictionary<TKey, TValue>(comparer);
}
public ConcurrentSortedDictionary(IDictionary<TKey, TValue> dictionary)
{
_dict = new SortedDictionary<TKey, TValue>(dictionary);
}
public ConcurrentSortedDictionary(IDictionary<TKey, TValue> dictionary, IComparer<TKey> comparer)
{
_dict = new SortedDictionary<TKey, TValue>(dictionary, comparer);
}
#endregion
#region Properties
public IComparer<TKey> Comparer
{
get
{
_readWriteLock.EnterReadLock();
try
{
return _dict.Comparer;
}
finally
{
_readWriteLock.ExitReadLock();
}
}
}
public int Count
{
get
{
_readWriteLock.EnterReadLock();
try
{
return _dict.Count;
}
finally
{
_readWriteLock.ExitReadLock();
}
}
}
public TValue this[TKey key]
{
get
{
_readWriteLock.EnterReadLock();
try
{
return _dict[key];
}
finally
{
_readWriteLock.ExitReadLock();
}
}
set
{
_readWriteLock.EnterWriteLock();
try
{
_dict[key] = value;
}
finally
{
_readWriteLock.ExitWriteLock();
}
}
}
public SortedDictionary<TKey, TValue>.KeyCollection Keys
{
get
{
_readWriteLock.EnterReadLock();
try
{
return new SortedDictionary<TKey, TValue>.KeyCollection(_dict);
}
finally
{
_readWriteLock.ExitReadLock();
}
}
}
public SortedDictionary<TKey, TValue>.ValueCollection Values
{
get
{
_readWriteLock.EnterReadLock();
try
{
return new SortedDictionary<TKey, TValue>.ValueCollection(_dict);
}
finally
{
_readWriteLock.ExitReadLock();
}
}
}
#endregion
#region Methods
public void Add(TKey key, TValue value)
{
_readWriteLock.EnterWriteLock();
try
{
_dict.Add(key, value);
}
finally
{
_readWriteLock.ExitWriteLock();
}
}
public void Clear()
{
_readWriteLock.EnterWriteLock();
try
{
_dict.Clear();
}
finally
{
_readWriteLock.ExitWriteLock();
}
}
public bool ContainsKey(TKey key)
{
_readWriteLock.EnterReadLock();
try
{
return _dict.ContainsKey(key);
}
finally
{
_readWriteLock.ExitReadLock();
}
}
public bool ContainsValue(TValue value)
{
_readWriteLock.EnterReadLock();
try
{
return _dict.ContainsValue(value);
}
finally
{
_readWriteLock.ExitReadLock();
}
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
{
_readWriteLock.EnterReadLock();
try
{
_dict.CopyTo(array, index);
}
finally
{
_readWriteLock.ExitReadLock();
}
}
public override bool Equals(Object obj)
{
_readWriteLock.EnterReadLock();
try
{
return _dict.Equals(obj);
}
finally
{
_readWriteLock.ExitReadLock();
}
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return new SafeEnumerator<KeyValuePair<TKey, TValue>>(_dict.GetEnumerator(), _readWriteLock);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new SafeEnumerator<KeyValuePair<TKey, TValue>>(_dict.GetEnumerator(), _readWriteLock);
}
public override int GetHashCode()
{
_readWriteLock.EnterReadLock();
try
{
return _dict.GetHashCode();
}
finally
{
_readWriteLock.ExitReadLock();
}
}
public bool Remove(TKey key)
{
_readWriteLock.EnterWriteLock();
try
{
return _dict.Remove(key);
}
finally
{
_readWriteLock.ExitWriteLock();
}
}
public override string ToString()
{
_readWriteLock.EnterReadLock();
try
{
return _dict.ToString();
}
finally
{
_readWriteLock.ExitReadLock();
}
}
public bool TryGetValue(TKey key, out TValue value)
{
_readWriteLock.EnterReadLock();
try
{
return _dict.TryGetValue(key, out value);
}
finally
{
_readWriteLock.ExitReadLock();
}
}
#endregion
}

No generic implementation of OrderedDictionary?

There doesn't appear to be a generic implementation of OrderedDictionary (which is in the System.Collections.Specialized namespace) in .NET 3.5. Is there one that I'm missing?
I've found implementations out there to provide the functionality, but wondered if/why there isn't a generic implementation out-of-the-box and if anyone knows whether it's something in .NET 4.0?
Implementing a generic OrderedDictionary isn't terribly difficult, but it's unnecessarily time consuming and frankly this class is a huge oversight on Microsoft's part. There are multiple ways of implementing this, but I chose to use a KeyedCollection for my internal storage. I also chose to implement various methods for sorting the way that List<T> does since this is essentially a hybrid IList and IDictionary. I've included my implementation here for posterity.
Here's the interface. Notice that it includes System.Collections.Specialized.IOrderedDictionary, which is the non-generic version of this interface that was provided by Microsoft.
// http://unlicense.org
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace mattmc3.Common.Collections.Generic {
public interface IOrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IOrderedDictionary {
new TValue this[int index] { get; set; }
new TValue this[TKey key] { get; set; }
new int Count { get; }
new ICollection<TKey> Keys { get; }
new ICollection<TValue> Values { get; }
new void Add(TKey key, TValue value);
new void Clear();
void Insert(int index, TKey key, TValue value);
int IndexOf(TKey key);
bool ContainsValue(TValue value);
bool ContainsValue(TValue value, IEqualityComparer<TValue> comparer);
new bool ContainsKey(TKey key);
new IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
new bool Remove(TKey key);
new void RemoveAt(int index);
new bool TryGetValue(TKey key, out TValue value);
TValue GetValue(TKey key);
void SetValue(TKey key, TValue value);
KeyValuePair<TKey, TValue> GetItem(int index);
void SetItem(int index, TValue value);
}
}
Here's the implementation along with helper classes:
// http://unlicense.org
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
namespace mattmc3.Common.Collections.Generic {
/// <summary>
/// A dictionary object that allows rapid hash lookups using keys, but also
/// maintains the key insertion order so that values can be retrieved by
/// key index.
/// </summary>
public class OrderedDictionary<TKey, TValue> : IOrderedDictionary<TKey, TValue> {
#region Fields/Properties
private KeyedCollection2<TKey, KeyValuePair<TKey, TValue>> _keyedCollection;
/// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <param name="key">The key associated with the value to get or set.</param>
public TValue this[TKey key] {
get {
return GetValue(key);
}
set {
SetValue(key, value);
}
}
/// <summary>
/// Gets or sets the value at the specified index.
/// </summary>
/// <param name="index">The index of the value to get or set.</param>
public TValue this[int index] {
get {
return GetItem(index).Value;
}
set {
SetItem(index, value);
}
}
public int Count {
get { return _keyedCollection.Count; }
}
public ICollection<TKey> Keys {
get {
return _keyedCollection.Select(x => x.Key).ToList();
}
}
public ICollection<TValue> Values {
get {
return _keyedCollection.Select(x => x.Value).ToList();
}
}
public IEqualityComparer<TKey> Comparer {
get;
private set;
}
#endregion
#region Constructors
public OrderedDictionary() {
Initialize();
}
public OrderedDictionary(IEqualityComparer<TKey> comparer) {
Initialize(comparer);
}
public OrderedDictionary(IOrderedDictionary<TKey, TValue> dictionary) {
Initialize();
foreach (KeyValuePair<TKey, TValue> pair in dictionary) {
_keyedCollection.Add(pair);
}
}
public OrderedDictionary(IOrderedDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
Initialize(comparer);
foreach (KeyValuePair<TKey, TValue> pair in dictionary) {
_keyedCollection.Add(pair);
}
}
#endregion
#region Methods
private void Initialize(IEqualityComparer<TKey> comparer = null) {
this.Comparer = comparer;
if (comparer != null) {
_keyedCollection = new KeyedCollection2<TKey, KeyValuePair<TKey, TValue>>(x => x.Key, comparer);
}
else {
_keyedCollection = new KeyedCollection2<TKey, KeyValuePair<TKey, TValue>>(x => x.Key);
}
}
public void Add(TKey key, TValue value) {
_keyedCollection.Add(new KeyValuePair<TKey, TValue>(key, value));
}
public void Clear() {
_keyedCollection.Clear();
}
public void Insert(int index, TKey key, TValue value) {
_keyedCollection.Insert(index, new KeyValuePair<TKey, TValue>(key, value));
}
public int IndexOf(TKey key) {
if (_keyedCollection.Contains(key)) {
return _keyedCollection.IndexOf(_keyedCollection[key]);
}
else {
return -1;
}
}
public bool ContainsValue(TValue value) {
return this.Values.Contains(value);
}
public bool ContainsValue(TValue value, IEqualityComparer<TValue> comparer) {
return this.Values.Contains(value, comparer);
}
public bool ContainsKey(TKey key) {
return _keyedCollection.Contains(key);
}
public KeyValuePair<TKey, TValue> GetItem(int index) {
if (index < 0 || index >= _keyedCollection.Count) {
throw new ArgumentException(String.Format("The index was outside the bounds of the dictionary: {0}", index));
}
return _keyedCollection[index];
}
/// <summary>
/// Sets the value at the index specified.
/// </summary>
/// <param name="index">The index of the value desired</param>
/// <param name="value">The value to set</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the index specified does not refer to a KeyValuePair in this object
/// </exception>
public void SetItem(int index, TValue value) {
if (index < 0 || index >= _keyedCollection.Count) {
throw new ArgumentException("The index is outside the bounds of the dictionary: {0}".FormatWith(index));
}
var kvp = new KeyValuePair<TKey, TValue>(_keyedCollection[index].Key, value);
_keyedCollection[index] = kvp;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
return _keyedCollection.GetEnumerator();
}
public bool Remove(TKey key) {
return _keyedCollection.Remove(key);
}
public void RemoveAt(int index) {
if (index < 0 || index >= _keyedCollection.Count) {
throw new ArgumentException(String.Format("The index was outside the bounds of the dictionary: {0}", index));
}
_keyedCollection.RemoveAt(index);
}
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">The key associated with the value to get.</param>
public TValue GetValue(TKey key) {
if (_keyedCollection.Contains(key) == false) {
throw new ArgumentException("The given key is not present in the dictionary: {0}".FormatWith(key));
}
var kvp = _keyedCollection[key];
return kvp.Value;
}
/// <summary>
/// Sets the value associated with the specified key.
/// </summary>
/// <param name="key">The key associated with the value to set.</param>
/// <param name="value">The the value to set.</param>
public void SetValue(TKey key, TValue value) {
var kvp = new KeyValuePair<TKey, TValue>(key, value);
var idx = IndexOf(key);
if (idx > -1) {
_keyedCollection[idx] = kvp;
}
else {
_keyedCollection.Add(kvp);
}
}
public bool TryGetValue(TKey key, out TValue value) {
if (_keyedCollection.Contains(key)) {
value = _keyedCollection[key].Value;
return true;
}
else {
value = default(TValue);
return false;
}
}
#endregion
#region sorting
public void SortKeys() {
_keyedCollection.SortByKeys();
}
public void SortKeys(IComparer<TKey> comparer) {
_keyedCollection.SortByKeys(comparer);
}
public void SortKeys(Comparison<TKey> comparison) {
_keyedCollection.SortByKeys(comparison);
}
public void SortValues() {
var comparer = Comparer<TValue>.Default;
SortValues(comparer);
}
public void SortValues(IComparer<TValue> comparer) {
_keyedCollection.Sort((x, y) => comparer.Compare(x.Value, y.Value));
}
public void SortValues(Comparison<TValue> comparison) {
_keyedCollection.Sort((x, y) => comparison(x.Value, y.Value));
}
#endregion
#region IDictionary<TKey, TValue>
void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
Add(key, value);
}
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) {
return ContainsKey(key);
}
ICollection<TKey> IDictionary<TKey, TValue>.Keys {
get { return Keys; }
}
bool IDictionary<TKey, TValue>.Remove(TKey key) {
return Remove(key);
}
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) {
return TryGetValue(key, out value);
}
ICollection<TValue> IDictionary<TKey, TValue>.Values {
get { return Values; }
}
TValue IDictionary<TKey, TValue>.this[TKey key] {
get {
return this[key];
}
set {
this[key] = value;
}
}
#endregion
#region ICollection<KeyValuePair<TKey, TValue>>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
_keyedCollection.Add(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
_keyedCollection.Clear();
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) {
return _keyedCollection.Contains(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
_keyedCollection.CopyTo(array, arrayIndex);
}
int ICollection<KeyValuePair<TKey, TValue>>.Count {
get { return _keyedCollection.Count; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
get { return false; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) {
return _keyedCollection.Remove(item);
}
#endregion
#region IEnumerable<KeyValuePair<TKey, TValue>>
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() {
return GetEnumerator();
}
#endregion
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
#endregion
#region IOrderedDictionary
IDictionaryEnumerator IOrderedDictionary.GetEnumerator() {
return new DictionaryEnumerator<TKey, TValue>(this);
}
void IOrderedDictionary.Insert(int index, object key, object value) {
Insert(index, (TKey)key, (TValue)value);
}
void IOrderedDictionary.RemoveAt(int index) {
RemoveAt(index);
}
object IOrderedDictionary.this[int index] {
get {
return this[index];
}
set {
this[index] = (TValue)value;
}
}
#endregion
#region IDictionary
void IDictionary.Add(object key, object value) {
Add((TKey)key, (TValue)value);
}
void IDictionary.Clear() {
Clear();
}
bool IDictionary.Contains(object key) {
return _keyedCollection.Contains((TKey)key);
}
IDictionaryEnumerator IDictionary.GetEnumerator() {
return new DictionaryEnumerator<TKey, TValue>(this);
}
bool IDictionary.IsFixedSize {
get { return false; }
}
bool IDictionary.IsReadOnly {
get { return false; }
}
ICollection IDictionary.Keys {
get { return (ICollection)this.Keys; }
}
void IDictionary.Remove(object key) {
Remove((TKey)key);
}
ICollection IDictionary.Values {
get { return (ICollection)this.Values; }
}
object IDictionary.this[object key] {
get {
return this[(TKey)key];
}
set {
this[(TKey)key] = (TValue)value;
}
}
#endregion
#region ICollection
void ICollection.CopyTo(Array array, int index) {
((ICollection)_keyedCollection).CopyTo(array, index);
}
int ICollection.Count {
get { return ((ICollection)_keyedCollection).Count; }
}
bool ICollection.IsSynchronized {
get { return ((ICollection)_keyedCollection).IsSynchronized; }
}
object ICollection.SyncRoot {
get { return ((ICollection)_keyedCollection).SyncRoot; }
}
#endregion
}
public class KeyedCollection2<TKey, TItem> : KeyedCollection<TKey, TItem> {
private const string DelegateNullExceptionMessage = "Delegate passed cannot be null";
private Func<TItem, TKey> _getKeyForItemDelegate;
public KeyedCollection2(Func<TItem, TKey> getKeyForItemDelegate)
: base() {
if (getKeyForItemDelegate == null) throw new ArgumentNullException(DelegateNullExceptionMessage);
_getKeyForItemDelegate = getKeyForItemDelegate;
}
public KeyedCollection2(Func<TItem, TKey> getKeyForItemDelegate, IEqualityComparer<TKey> comparer)
: base(comparer) {
if (getKeyForItemDelegate == null) throw new ArgumentNullException(DelegateNullExceptionMessage);
_getKeyForItemDelegate = getKeyForItemDelegate;
}
protected override TKey GetKeyForItem(TItem item) {
return _getKeyForItemDelegate(item);
}
public void SortByKeys() {
var comparer = Comparer<TKey>.Default;
SortByKeys(comparer);
}
public void SortByKeys(IComparer<TKey> keyComparer) {
var comparer = new Comparer2<TItem>((x, y) => keyComparer.Compare(GetKeyForItem(x), GetKeyForItem(y)));
Sort(comparer);
}
public void SortByKeys(Comparison<TKey> keyComparison) {
var comparer = new Comparer2<TItem>((x, y) => keyComparison(GetKeyForItem(x), GetKeyForItem(y)));
Sort(comparer);
}
public void Sort() {
var comparer = Comparer<TItem>.Default;
Sort(comparer);
}
public void Sort(Comparison<TItem> comparison) {
var newComparer = new Comparer2<TItem>((x, y) => comparison(x, y));
Sort(newComparer);
}
public void Sort(IComparer<TItem> comparer) {
List<TItem> list = base.Items as List<TItem>;
if (list != null) {
list.Sort(comparer);
}
}
}
public class Comparer2<T> : Comparer<T> {
//private readonly Func<T, T, int> _compareFunction;
private readonly Comparison<T> _compareFunction;
#region Constructors
public Comparer2(Comparison<T> comparison) {
if (comparison == null) throw new ArgumentNullException("comparison");
_compareFunction = comparison;
}
#endregion
public override int Compare(T arg1, T arg2) {
return _compareFunction(arg1, arg2);
}
}
public class DictionaryEnumerator<TKey, TValue> : IDictionaryEnumerator, IDisposable {
readonly IEnumerator<KeyValuePair<TKey, TValue>> impl;
public void Dispose() { impl.Dispose(); }
public DictionaryEnumerator(IDictionary<TKey, TValue> value) {
this.impl = value.GetEnumerator();
}
public void Reset() { impl.Reset(); }
public bool MoveNext() { return impl.MoveNext(); }
public DictionaryEntry Entry {
get {
var pair = impl.Current;
return new DictionaryEntry(pair.Key, pair.Value);
}
}
public object Key { get { return impl.Current.Key; } }
public object Value { get { return impl.Current.Value; } }
public object Current { get { return Entry; } }
}
}
And no implementation would be complete without a few tests (but tragically, SO won't let me post that much code in one post), so I'll have to leave you to write your tests. But, I left a few of them in so that you could get an idea of how it works:
// http://unlicense.org
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using mattmc3.Common.Collections.Generic;
namespace mattmc3.Tests.Common.Collections.Generic {
[TestClass]
public class OrderedDictionaryTests {
private OrderedDictionary<string, string> GetAlphabetDictionary(IEqualityComparer<string> comparer = null) {
OrderedDictionary<string, string> alphabet = (comparer == null ? new OrderedDictionary<string, string>() : new OrderedDictionary<string, string>(comparer));
for (var a = Convert.ToInt32('a'); a <= Convert.ToInt32('z'); a++) {
var c = Convert.ToChar(a);
alphabet.Add(c.ToString(), c.ToString().ToUpper());
}
Assert.AreEqual(26, alphabet.Count);
return alphabet;
}
private List<KeyValuePair<string, string>> GetAlphabetList() {
var alphabet = new List<KeyValuePair<string, string>>();
for (var a = Convert.ToInt32('a'); a <= Convert.ToInt32('z'); a++) {
var c = Convert.ToChar(a);
alphabet.Add(new KeyValuePair<string, string>(c.ToString(), c.ToString().ToUpper()));
}
Assert.AreEqual(26, alphabet.Count);
return alphabet;
}
[TestMethod]
public void TestAdd() {
var od = new OrderedDictionary<string, string>();
Assert.AreEqual(0, od.Count);
Assert.AreEqual(-1, od.IndexOf("foo"));
od.Add("foo", "bar");
Assert.AreEqual(1, od.Count);
Assert.AreEqual(0, od.IndexOf("foo"));
Assert.AreEqual(od[0], "bar");
Assert.AreEqual(od["foo"], "bar");
Assert.AreEqual(od.GetItem(0).Key, "foo");
Assert.AreEqual(od.GetItem(0).Value, "bar");
}
[TestMethod]
public void TestRemove() {
var od = new OrderedDictionary<string, string>();
od.Add("foo", "bar");
Assert.AreEqual(1, od.Count);
od.Remove("foo");
Assert.AreEqual(0, od.Count);
}
[TestMethod]
public void TestRemoveAt() {
var od = new OrderedDictionary<string, string>();
od.Add("foo", "bar");
Assert.AreEqual(1, od.Count);
od.RemoveAt(0);
Assert.AreEqual(0, od.Count);
}
[TestMethod]
public void TestClear() {
var od = GetAlphabetDictionary();
Assert.AreEqual(26, od.Count);
od.Clear();
Assert.AreEqual(0, od.Count);
}
[TestMethod]
public void TestOrderIsPreserved() {
var alphabetDict = GetAlphabetDictionary();
var alphabetList = GetAlphabetList();
Assert.AreEqual(26, alphabetDict.Count);
Assert.AreEqual(26, alphabetList.Count);
var keys = alphabetDict.Keys.ToList();
var values = alphabetDict.Values.ToList();
for (var i = 0; i < 26; i++) {
var dictItem = alphabetDict.GetItem(i);
var listItem = alphabetList[i];
var key = keys[i];
var value = values[i];
Assert.AreEqual(dictItem, listItem);
Assert.AreEqual(key, listItem.Key);
Assert.AreEqual(value, listItem.Value);
}
}
[TestMethod]
public void TestTryGetValue() {
var alphabetDict = GetAlphabetDictionary();
string result = null;
Assert.IsFalse(alphabetDict.TryGetValue("abc", out result));
Assert.IsNull(result);
Assert.IsTrue(alphabetDict.TryGetValue("z", out result));
Assert.AreEqual("Z", result);
}
[TestMethod]
public void TestEnumerator() {
var alphabetDict = GetAlphabetDictionary();
var keys = alphabetDict.Keys.ToList();
Assert.AreEqual(26, keys.Count);
var i = 0;
foreach (var kvp in alphabetDict) {
var value = alphabetDict[kvp.Key];
Assert.AreEqual(kvp.Value, value);
i++;
}
}
[TestMethod]
public void TestInvalidIndex() {
var alphabetDict = GetAlphabetDictionary();
try {
var notGonnaWork = alphabetDict[100];
Assert.IsTrue(false, "Exception should have thrown");
}
catch (Exception ex) {
Assert.IsTrue(ex.Message.Contains("index is outside the bounds"));
}
}
[TestMethod]
public void TestMissingKey() {
var alphabetDict = GetAlphabetDictionary();
try {
var notGonnaWork = alphabetDict["abc"];
Assert.IsTrue(false, "Exception should have thrown");
}
catch (Exception ex) {
Assert.IsTrue(ex.Message.Contains("key is not present"));
}
}
[TestMethod]
public void TestUpdateExistingValue() {
var alphabetDict = GetAlphabetDictionary();
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("c"));
Assert.AreEqual(alphabetDict[2], "C");
alphabetDict[2] = "CCC";
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("c"));
Assert.AreEqual(alphabetDict[2], "CCC");
}
[TestMethod]
public void TestInsertValue() {
var alphabetDict = GetAlphabetDictionary();
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("c"));
Assert.AreEqual(alphabetDict[2], "C");
Assert.AreEqual(26, alphabetDict.Count);
Assert.IsFalse(alphabetDict.ContainsValue("ABC"));
alphabetDict.Insert(2, "abc", "ABC");
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("abc"));
Assert.AreEqual(alphabetDict[2], "ABC");
Assert.AreEqual(27, alphabetDict.Count);
Assert.IsTrue(alphabetDict.ContainsValue("ABC"));
}
[TestMethod]
public void TestValueComparer() {
var alphabetDict = GetAlphabetDictionary();
Assert.IsFalse(alphabetDict.ContainsValue("a"));
Assert.IsTrue(alphabetDict.ContainsValue("a", StringComparer.OrdinalIgnoreCase));
}
[TestMethod]
public void TestSortByKeys() {
var alphabetDict = GetAlphabetDictionary();
var reverseAlphabetDict = GetAlphabetDictionary();
Comparison<string> stringReverse = ((x, y) => (String.Equals(x, y) ? 0 : String.Compare(x, y) >= 1 ? -1 : 1));
reverseAlphabetDict.SortKeys(stringReverse);
for (int j = 0, k = 25; j < alphabetDict.Count; j++, k--) {
var ascValue = alphabetDict.GetItem(j);
var dscValue = reverseAlphabetDict.GetItem(k);
Assert.AreEqual(ascValue.Key, dscValue.Key);
Assert.AreEqual(ascValue.Value, dscValue.Value);
}
}
-- UPDATE --
Source for this and other really useful missing core .NET libraries here: https://github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrderedDictionary.cs
You're right. There's no generic equivalent of OrderedDictionary in the framework itself.
(That's still the case for .NET 4 too, as far as I'm aware.)
For the record, there is a generic KeyedCollection that allows objects to be indexed by an int and a key. The key must be embedded in the value.
Here's a bizarre find: the System.Web.Util namespace in System.Web.Extensions.dll contains a generic OrderedDictionary<TKey,TValue>
// Type: System.Web.Util.OrderedDictionary`2
// Assembly: System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Web.Extensions.dll
namespace System.Web.Util
{
internal class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable
Not sure why MS placed it there instead of the System.Collections.Generic package, but I assume you can simply copy paste the code and use it (it's internal, so can't use it directly). Looks like the implementation uses a standard dictionary and separate Key/Value lists. Pretty straightforward...
Source code: https://referencesource.microsoft.com/#System.Web.Extensions/Util/OrderedDictionary.cs
A different implementation in System.Runtime.Collections that wraps the non-generic System.Collections.Specialized.OrderedDictionary: https://referencesource.microsoft.com/#System.ServiceModel.Internals/System/Runtime/Collections/OrderedDictionary.cs
For what it's worth, here is how I solved it:
public class PairList<TKey, TValue> : List<KeyValuePair<TKey, TValue>> {
Dictionary<TKey, int> itsIndex = new Dictionary<TKey, int>();
public void Add(TKey key, TValue value) {
Add(new KeyValuePair<TKey, TValue>(key, value));
itsIndex.Add(key, Count-1);
}
public TValue Get(TKey key) {
var idx = itsIndex[key];
return this[idx].Value;
}
}
It can be initialized like this:
var pairList = new PairList<string, string>
{
{ "pitcher", "Ken" },
{ "catcher", "Brad"},
{ "left fielder", "Stan"},
};
and accessed like this:
foreach (var pair in pairList)
{
Console.WriteLine("position: {0}, player: {1}",
pair.Key, pair.Value);
}
// Guaranteed to print in the order of initialization
A major conceptual problem with a generic version of OrderedDictionary is that users of a OrderedDictionary<TKey,TValue> would expect expect to be able to index it either numerically using an int, or by lookup using a TKey. When the only type of key was Object, as was the case with non-generic OrderedDictionary, the type of argument passed to the indexer would be sufficient to distinguish whether what type of indexing operation should be performed. As it is, though, it's unclear how the indexer of an OrderedDictionary<int, TValue> should behave.
If classes like Drawing.Point had recommended and followed a rule that piecewise-mutable structures should expose their mutable elements as fields rather than properties, and refrain from using property setters that modify this, then an OrderedDictionary<TKey,TValue> could efficiently expose a ByIndex property that returned an Indexer struct which held a reference to the dictionary, and had an indexed property whose getter and setter would call GetByIndex and SetByIndex upon it. Thus, one could say something like MyDict.ByIndex[5] += 3; to add 3 to the sixth element of the dictionary.
Unfortunately, for the compiler to accept such a thing, it would be necessary to make the ByIndex property return a new class instance rather than a struct every time it's invoked, eliminating the advantages one would get by avoiding boxing.
In VB.NET, one could get around that issue by using a named indexed property (so MyDict.ByIndex[int] would be a member of MyDict, rather than requiring MyDict.ByIndex to be a member of MyDict which includes an indexer), but C# doesn't allow such things.
It might still have been worthwhile to offer an OrderedDictionary<TKey,TValue> where TKey:class, but much of the reason for providing generics in the first place was to allow their use with value types.
For a lot of purposes I've found one can get by with a List<KeyValuePair<K, V>>. (Not if you need it to extend Dictionary, obviously, and not if you need better than O(n) key-value lookup.)
Right, it's an unfortunate omission. I miss Python's OrderedDict
A dictionary that remembers the order that keys were first inserted. If a new entry overwrites an existing entry, the original insertion position is left unchanged. Deleting an entry and reinserting it will move it to the end.
So I wrote my own OrderedDictionary<K,V> class in C#. How does it work? It maintains two collections - a vanilla unordered dictionary and an ordered list of keys. With this solution, the standard dictionary operations keep their fast complexities, and look up by index is fast too.
https://gist.github.com/hickford/5137384
Here's the interface
/// <summary>
/// A dictionary that remembers the order that keys were first inserted. If a new entry overwrites an existing entry, the original insertion position is left unchanged. Deleting an entry and reinserting it will move it to the end.
/// </summary>
/// <typeparam name="TKey">The type of keys</typeparam>
/// <typeparam name="TValue">The type of values</typeparam>
public interface IOrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
/// <summary>
/// The value of the element at the given index.
/// </summary>
TValue this[int index] { get; set; }
/// <summary>
/// Find the position of an element by key. Returns -1 if the dictionary does not contain an element with the given key.
/// </summary>
int IndexOf(TKey key);
/// <summary>
/// Insert an element at the given index.
/// </summary>
void Insert(int index, TKey key, TValue value);
/// <summary>
/// Remove the element at the given index.
/// </summary>
void RemoveAt(int index);
}
For those looking for an "official" package option in NuGet, an implementation of a generic OrderedDictionary has been accepted into .NET CoreFX Lab. If all goes well, the type will eventually be approved and integrated to the main .NET CoreFX repo.
There is a possibility that this implementation will be rejected.
The committed implementation can be referenced here
https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/OrderedDictionary.cs
The NuGet package that definitively has this type available for use can be found here
https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3
Or you can install the package within Visual Studio. Browse for the package "Microsoft.Experimental.Collections" and make sure the "Include prerelease" checkbox is selected.
Will update this post if and when the type is made officially available.
There is SortedDictionary<TKey, TValue>. Although semantically close, I am not claiming it's the same as OrderedDictionary simply because they are not. Even from performance characteristics. However the very interesting and quite important difference between Dictionary<TKey, TValue> (and to that extent OrderedDictionary and implementations provided in answers) and SortedDictionary is that the latter is using binary tree underneath. This is critical distinction because it makes the class immune to memory constraints applied to generic class. See this thread about OutOfMemoryExceptions thrown when generic class is used for handling large set of key-value pairs.
How to figure out the max value for capacity parameter passed to Dictionary constructor to avoid OutOfMemoryException?
As a follow up to the comment from #V.B. here's an accessible implementation of the System.Runtime.Collections.OrderedDictionary<,>. I was originally going to access it by reflection and provide it via a factory but the dll this is in does not seem to be very accessible at all so I just pulled the source itself.
One thing to note is the indexer here will not throw KeyNotFoundException. I absolutely hate that convention and that was the 1 liberty i took in this implementation. If that's important to you, just replace the line for return default(TValue);. Uses C# 6 (compatible with Visual Studio 2013)
/// <summary>
/// System.Collections.Specialized.OrderedDictionary is NOT generic.
/// This class is essentially a generic wrapper for OrderedDictionary.
/// </summary>
/// <remarks>
/// Indexer here will NOT throw KeyNotFoundException
/// </remarks>
public class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
{
private readonly OrderedDictionary _privateDictionary;
public OrderedDictionary()
{
_privateDictionary = new OrderedDictionary();
}
public OrderedDictionary(IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null) return;
_privateDictionary = new OrderedDictionary();
foreach (var pair in dictionary)
{
_privateDictionary.Add(pair.Key, pair.Value);
}
}
public bool IsReadOnly => false;
public int Count => _privateDictionary.Count;
int ICollection.Count => _privateDictionary.Count;
object ICollection.SyncRoot => ((ICollection)_privateDictionary).SyncRoot;
bool ICollection.IsSynchronized => ((ICollection)_privateDictionary).IsSynchronized;
bool IDictionary.IsFixedSize => ((IDictionary)_privateDictionary).IsFixedSize;
bool IDictionary.IsReadOnly => _privateDictionary.IsReadOnly;
ICollection IDictionary.Keys => _privateDictionary.Keys;
ICollection IDictionary.Values => _privateDictionary.Values;
void IDictionary.Add(object key, object value)
{
_privateDictionary.Add(key, value);
}
void IDictionary.Clear()
{
_privateDictionary.Clear();
}
bool IDictionary.Contains(object key)
{
return _privateDictionary.Contains(key);
}
IDictionaryEnumerator IDictionary.GetEnumerator()
{
return _privateDictionary.GetEnumerator();
}
void IDictionary.Remove(object key)
{
_privateDictionary.Remove(key);
}
object IDictionary.this[object key]
{
get { return _privateDictionary[key]; }
set { _privateDictionary[key] = value; }
}
void ICollection.CopyTo(Array array, int index)
{
_privateDictionary.CopyTo(array, index);
}
public TValue this[TKey key]
{
get
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (_privateDictionary.Contains(key))
{
return (TValue) _privateDictionary[key];
}
return default(TValue);
}
set
{
if (key == null) throw new ArgumentNullException(nameof(key));
_privateDictionary[key] = value;
}
}
public ICollection<TKey> Keys
{
get
{
var keys = new List<TKey>(_privateDictionary.Count);
keys.AddRange(_privateDictionary.Keys.Cast<TKey>());
return keys.AsReadOnly();
}
}
public ICollection<TValue> Values
{
get
{
var values = new List<TValue>(_privateDictionary.Count);
values.AddRange(_privateDictionary.Values.Cast<TValue>());
return values.AsReadOnly();
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Add(TKey key, TValue value)
{
if (key == null) throw new ArgumentNullException(nameof(key));
_privateDictionary.Add(key, value);
}
public void Clear()
{
_privateDictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
if (item.Key == null || !_privateDictionary.Contains(item.Key))
{
return false;
}
return _privateDictionary[item.Key].Equals(item.Value);
}
public bool ContainsKey(TKey key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return _privateDictionary.Contains(key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
if (array.Rank > 1 || arrayIndex >= array.Length
|| array.Length - arrayIndex < _privateDictionary.Count)
throw new ArgumentException("Bad Copy ToArray", nameof(array));
var index = arrayIndex;
foreach (DictionaryEntry entry in _privateDictionary)
{
array[index] =
new KeyValuePair<TKey, TValue>((TKey) entry.Key, (TValue) entry.Value);
index++;
}
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
foreach (DictionaryEntry entry in _privateDictionary)
{
yield return
new KeyValuePair<TKey, TValue>((TKey) entry.Key, (TValue) entry.Value);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
if (false == Contains(item)) return false;
_privateDictionary.Remove(item.Key);
return true;
}
public bool Remove(TKey key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (false == _privateDictionary.Contains(key)) return false;
_privateDictionary.Remove(key);
return true;
}
public bool TryGetValue(TKey key, out TValue value)
{
if (key == null) throw new ArgumentNullException(nameof(key));
var keyExists = _privateDictionary.Contains(key);
value = keyExists ? (TValue) _privateDictionary[key] : default(TValue);
return keyExists;
}
}
Pull requests/discussion accepted on GitHub
I implemented a generic OrderedDictionary<TKey, TValue> by wraping around SortedList<TKey, TValue> and adding a private Dictionary<TKey, int> _order. Then I created an internal implementation of Comparer<TKey>, passing a reference to the _order dictionary. Then I use this comparer for the internal SortedList. This class keeps the order of elements passed to the constructor and order of additions.
This implementation has almost the same big O characteristics as SortedList<TKey, TValue> since adding and removing to _order is O(1). Each element will take (according to the book 'C# 4 in a Nutshell', p. 292, table 7-1) additional memory space of 22 (overhead) + 4 (int order) + TKey size (let's assume 8) = 34. Together with SortedList<TKey, TValue>'s overhead of two bytes, the total overhead is 36 bytes, while the same book says that non-generic OrderedDictionary has an overhead of 59 bytes.
If I pass sorted=true to constructor, then _order is not used at all, the OrderedDictionary<TKey, TValue> is exactly SortedList<TKey, TValue> with minor overhead for wrapping, if at all meaningful.
I am going to store not-so-many large reference objects in the OrderedDictionary<TKey, TValue>, so for me this ca. 36 bytes overhead is tolerable.
The main code is below. The complete updated code is on this gist.
public class OrderedList<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
{
private readonly Dictionary<TKey, int> _order;
private readonly SortedList<TKey, TValue> _internalList;
private readonly bool _sorted;
private readonly OrderComparer _comparer;
public OrderedList(IDictionary<TKey, TValue> dictionary, bool sorted = false)
{
_sorted = sorted;
if (dictionary == null)
dictionary = new Dictionary<TKey, TValue>();
if (_sorted)
{
_internalList = new SortedList<TKey, TValue>(dictionary);
}
else
{
_order = new Dictionary<TKey, int>();
_comparer = new OrderComparer(ref _order);
_internalList = new SortedList<TKey, TValue>(_comparer);
// Keep order of the IDictionary
foreach (var kvp in dictionary)
{
Add(kvp);
}
}
}
public OrderedList(bool sorted = false)
: this(null, sorted)
{
}
private class OrderComparer : Comparer<TKey>
{
public Dictionary<TKey, int> Order { get; set; }
public OrderComparer(ref Dictionary<TKey, int> order)
{
Order = order;
}
public override int Compare(TKey x, TKey y)
{
var xo = Order[x];
var yo = Order[y];
return xo.CompareTo(yo);
}
}
private void ReOrder()
{
var i = 0;
_order = _order.OrderBy(kvp => kvp.Value).ToDictionary(kvp => kvp.Key, kvp => i++);
_comparer.Order = _order;
_lastOrder = _order.Values.Max() + 1;
}
public void Add(TKey key, TValue value)
{
if (!_sorted)
{
_order.Add(key, _lastOrder);
_lastOrder++;
// Very rare event
if (_lastOrder == int.MaxValue)
ReOrder();
}
_internalList.Add(key, value);
}
public bool Remove(TKey key)
{
var result = _internalList.Remove(key);
if (!_sorted)
_order.Remove(key);
return result;
}
// Other IDictionary<> + IDictionary members implementation wrapping around _internalList
// ...
}
This is not yet another version/solution of an OrderedDictionary<,> but an experiment I did testing each of 4 versions mentioned in the answers: of #Colonel Panic, #mattmc3, #V.B. #Chris Marisic. It is meant as a feedback. Well, partial because I have to admit I haven't dissected the code, so there may be differences in functionality or safety checks. But still, I thought feedback would be useful on their performance. And as you'll see time can get from a couple of milliseconds to a quarter of hour.
Then I scribbled a naive minimal version with 2 lists of key and value class objects with O(n) search just to see the magnitude of the benefit of O(1) access.
Testbed is Microsoft Visual Studio Community 2019 with Unity 3D, 4 consecutive times for each test and the code that I wanted to replicate a real-ish scenario in is
using System.Text;
using UnityEngine;
public class TessyOne : MonoBehaviour
{
public const int iterations = 50000;
private System.Diagnostics.Stopwatch stopwatch;
private System.Random random;
public float stopwatchDuration;
public class Ala
{
public int inta;
public float fla;
public string stra;
public Ben bena;
public Ala(int i, float f, string s, Ben b)
{
inta = i; fla = f; stra = s; bena = b;
}
}
public class Ben
{
public int inte;
public float fle;
public string stre;
public Ben(int i, float f, string s)
{
inte = i; fle = f; stre = s;
}
}
//public Naive.OrderedDictionary<Ala, Ben> alasToBens = new Naive.OrderedDictionary<Ala, Ben>();
//public Hickford.OrderedDictionary<Ala, Ben> alasToBens = new Hickford.OrderedDictionary<Ala, Ben>();
//public Mattmc3.OrderedDictionary<Ala, Ben> alasToBens = new Mattmc3.OrderedDictionary<Ala, Ben>();
public Marisic.OrderedDictionary<Ala, Ben> alasToBens = new Marisic.OrderedDictionary<Ala, Ben>();
//public VB.OrderedList<Ala, Ben> alasToBens = new VB.OrderedList<Ala, Ben>(null, false);
Ala[] alarray = new Ala[iterations];
Ben[] berray = new Ben[iterations];
// This is the entry point of the application
private void Start()
{
stopwatch = new System.Diagnostics.Stopwatch();
random = new System.Random(2020);
for(int i = 0; i < iterations; ++i)
{
berray[i] = new Ben(random.Next(),
(float)random.NextDouble(),
MakeRandomString((ushort)random.Next(1, 10)));
alarray[i] = new Ala(random.Next(),
(float)random.NextDouble(),
MakeRandomString((ushort)random.Next(1, 10)),
berray[i]);
// uncomment for testing ContainsKey() and Remove(), comment for Add()
alasToBens.Add(alarray[i], berray[i]);
}
stopwatch.Start();
for(int i = iterations - 1; i > -1; --i)
{
//alasToBens.Add(alarray[i], berray[i]);
//alasToBens.ContainsKey(alarray[i]);
alasToBens.Remove(alarray[i]);
}
stopwatch.Stop();
stopwatchDuration = stopwatch.ElapsedMilliseconds;
}
public string MakeRandomString(ushort length)
{
StringBuilder sb = new StringBuilder();
for(ushort u = 0; u < length; ++u)
{
sb.Append((char)Random.Range(33, 126)); // regular ASCII chars
}
return sb.ToString();
}
}
Note that the tests are for worst case scenarios in the case of naive version at least, as it iterates through the collection from index 0 through iterations and searching is done from end to start. I measured Add(), ContainsKey() and Remove() in milliseconds for a dictionary of 50000 entries.
Results:
+----------+----------------+----------------+--------------------------------+
| ms | Add() | ContainsKey() | Remove() |
+----------+----------------+----------------+--------------------------------+
| Hickford | 7, 8, 7, 8 | 2, 2, 3, 2 | 7400, 7503, 7419, 7421 |
| Mattmc3 | 23, 24, 24, 23 | 3, 3, 3, 3 | 890404, 913465, 875387, 877792 |
| Marisic | 27, 28, 28, 27 | 4, 4, 4, 4 | 27401, 27627, 27341, 27349 |
| V.B. | 76, 76, 75, 75 | 59, 60, 60, 60 | 66, 67, 67, 67 |
| | | | |
| Naive | 19651, 19761 | 25335, 25416 | 25259, 25306 |
+----------+----------------+----------------+--------------------------------+

Categories

Resources