Why is ConcurrentDictionary.AddOrUpdate method slow? - c#

I am working on a thread safe multi valued dictionary. Internally this dictionary uses a Concurrent dictionary (.net 4.0) with a custom linklist as value. Same key items are added in the linklist. The problem is when I use concurrent dictionary's AddOrUpdate method (approach 1) to insert an item, the code runs a bit slow as compared to when I use a TryGetValue method to check whether the key is present or not and then add or update the value manually inside a lock (approach 2). It takes around 20 seconds to insert 3 million records using the first approach, whereas using the second approach it takes around 9.5 seconds on a same machine (Intel i3 2nd generation 2.2 ghz & 4 Gb ram). There must be something missing which I am not able to figure out yet.
I have also checked the code for concurrent dictionary but it seems to do the same thing as I am doing inside a lock:
public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
{
if (key == null) throw new ArgumentNullException("key");
if (addValueFactory == null) throw new ArgumentNullException("addValueFactory");
if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory");
TValue newValue, resultingValue;
while (true)
{
TValue oldValue;
if (TryGetValue(key, out oldValue))
//key exists, try to update
{
newValue = updateValueFactory(key, oldValue);
if (TryUpdate(key, newValue, oldValue))
{
return newValue;
}
}
else //try add
{
newValue = addValueFactory(key);
if (TryAddInternal(key, newValue, false, true, out resultingValue))
{
return resultingValue;
}
}
}
}
Here is the code for thread safe multi valued dictionary (approach 2 is commented, uncomment it to check the difference).
Update: There are Remove, Add and other methods also which I have not pasted below.
class ValueWrapper<U, V>
{
private U _key;
private V _value;
public ValueWrapper(U key, V value)
{
this._key = key;
this._value = value;
}
public U Key
{
get { return _key; }
}
public V Value
{
get { return _value; }
set { _value = value; }
}
}
class LinkNode<Type>
{
public LinkNode(Type data)
{
Data = data;
}
public LinkNode<Type> Next;
public Type Data;
}
public class SimpleLinkedList<T>
{
#region Instance Member Variables
private LinkNode<T> _startNode = null;
private LinkNode<T> _endNode = null;
private int _count = 0;
#endregion
public void AddAtLast(T item)
{
if (_endNode == null)
_endNode = _startNode = new LinkNode<T>(item);
else
{
LinkNode<T> node = new LinkNode<T>(item);
_endNode.Next = node;
_endNode = node;
}
_count++;
}
public T First
{
get { return _startNode == null ? default(T) : _startNode.Data; }
}
public int Count
{
get { return _count; }
}
}
class MultiValThreadSafeDictionary<U, T>
{
private ConcurrentDictionary<U, SimpleLinkedList<ValueWrapper<U, T>>> _internalDictionary;
private ReaderWriterLockSlim _slimLock = new ReaderWriterLockSlim();
public MultiValThreadSafeDictionary()
{
_internalDictionary = new ConcurrentDictionary<U, SimpleLinkedList<ValueWrapper<U, T>>>(2, 100);
}
public T this[U key]
{
get
{
throw new NotImplementedException();
}
set
{
/* ****Approach 1 using AddOrUpdate**** */
_internalDictionary.AddOrUpdate(key, (x) =>
{
SimpleLinkedList<ValueWrapper<U, T>> list = new SimpleLinkedList<ValueWrapper<U, T>>();
ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
list.AddAtLast(vw);
//_internalDictionary[key] = list;
return list;
},
(k, existingList) =>
{
try
{
_slimLock.EnterWriteLock();
if (existingList.Count == 0)
{
ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
existingList.AddAtLast(vw);
}
else
existingList.First.Value = value;
return existingList;
}
finally
{
_slimLock.ExitWriteLock();
}
});
/* ****Approach 2 not using AddOrUpdate**** */
/*
try
{
_slimLock.EnterWriteLock();
SimpleLinkedList<ValueWrapper<U, T>> list;
if (!_internalDictionary.TryGetValue(key, out list))
{
list = new SimpleLinkedList<ValueWrapper<U, T>>();
ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
list.AddAtLast(vw);
_internalDictionary[key] = list;
//_iterator.AddAtLast(vw);
return;
}
if (list.Count == 0)
{
ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
list.AddAtLast(vw);
//_iterator.AddAtLast(vw);
}
else
list.First.Value = value;
}
finally
{
_slimLock.ExitWriteLock();
}
*/
}
}
}
The test code only insert items, all with unique keys. It is as follows.
MultiValThreadSafeDictionary<string, int> testData = new MultiValThreadSafeDictionary<string, int>();
Task t1 = new Task(() =>
{
for (int i = 0; i < 1000000; i++)
{
testData[i.ToString()] = i;
}
}
);
Task t2 = new Task(() =>
{
for (int i = 1000000; i < 2000000; i++)
{
testData[i.ToString()] = i;
}
}
);
Task t3 = new Task(() =>
{
for (int i = 2000000; i < 3000000; i++)
{
testData[i.ToString()] = i;
}
}
);
Stopwatch watch = new Stopwatch();
watch.Start();
t1.Start();
t2.Start();
t3.Start();
t1.Wait();
t2.Wait();
t3.Wait();
watch.Stop();
Console.WriteLine("time taken:" + watch.ElapsedMilliseconds);
Update 1:
Based on the answer from '280Z28', I am rephrasing the question. Why is GetOrAdd and 'my' method taking almost the same time, where as in my method I am taking an extra lock and also calling TryAndGet method also. And why AddOrUpdate taking the double amount of time as compared to AddOrGet. Code for all of the approaches is as under:
GetOrAdd and AddOrUpdate method in ConcurrentDictionary (.net 4) has the following code:
public TValue GetOrAdd(TKey key, TValue value)
{
if (key == null) throw new ArgumentNullException("key");
TValue resultingValue;
TryAddInternal(key, value, false, true, out resultingValue);
return resultingValue;
}
public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
{
if (key == null) throw new ArgumentNullException("key");
if (addValueFactory == null) throw new ArgumentNullException("addValueFactory");
if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory");
TValue newValue, resultingValue;
while (true)
{
TValue oldValue;
if (TryGetValue(key, out oldValue))
//key exists, try to update
{
newValue = updateValueFactory(key, oldValue);
if (TryUpdate(key, newValue, oldValue))
{
return newValue;
}
}
else //try add
{
newValue = addValueFactory(key);
if (TryAddInternal(key, newValue, false, true, out resultingValue))
{
return resultingValue;
}
}
}
}
GetOrAdd in my code is used as follows (taking 9 seconds):
SimpleLinkedList<ValueWrapper<U, T>> existingList = new SimpleLinkedList<ValueWrapper<U, T>>();
existingList = _internalDictionary.GetOrAdd(key, existingList);
try
{
_slimLock.EnterWriteLock();
if (existingList.Count == 0)
{
ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
existingList.AddAtLast(vw);
}
else
existingList.First.Value = value;
}
finally
{
_slimLock.ExitWriteLock();
}
AddOrUpdate is used as follows (taking 20 seconds on all adds, no updates). As described in one of the answers this approach is not suitable for update.
_internalDictionary.AddOrUpdate(key, (x) =>
{
SimpleLinkedList<ValueWrapper<U, T>> list = new SimpleLinkedList<ValueWrapper<U, T>>();
ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
list.AddAtLast(vw);
return list;
},
(k, existingList ) =>
{
try
{
_slimLock.EnterWriteLock();
if (existingList.Count == 0)
{
ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
existingList.AddAtLast(vw);
}
else
existingList.First.Value = value;
return existingList;
}
finally
{
_slimLock.ExitWriteLock();
}
});
Code without AddOrGet and AddOrUpdate is as follows (taking 9.5 seconds):
try
{
_slimLock.EnterWriteLock();
VerySimpleLinkedList<ValueWrapper<U, T>> list;
if (!_internalDictionary.TryGetValue(key, out list))
{
list = new VerySimpleLinkedList<ValueWrapper<U, T>>();
ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
list.AddAtLast(vw);
_internalDictionary[key] = list;
return;
}
if (list.Count == 0)
{
ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
list.AddAtLast(vw);
}
else
list.First.Value = value;
}
finally
{
_slimLock.ExitWriteLock();
}

You should not be using AddOrUpdate for this code. This is extremely clear because your update method never actually updates the value stored in ConcurrentDictionary - it always returns the existingList argument unchanged. Instead, you should be doing something like the following.
SimpleLinkedList<ValueWrapper<U, T>> list = _internalDictionary.GetOrAdd(key, CreateEmptyList);
// operate on list here
...
private static SimpleLinkedList<ValueWrapper<U, T>> CreateEmptyList()
{
return new SimpleLinkedList<ValueWrapper<U, T>>();
}

Read operations on the dictionary are performed in a lock-free manner.
As mentioned in http://msdn.microsoft.com/en-us/library/dd287191.aspx
Implementation of AddOrUpdate is using fine-grained lock so to check if item already exists or not, but when you first read by yourself, being lock free read it is faster and by doing that you are reducing locks required for existing items.

Related

TPL DataFlow Queue with Postponement

I am processing a queue concurrently using an ActionBlock.
The one catch here is that when processing an item in the queue, I may want to wait until a dependency is satisfied by the processing of another item in the queue.
I think I should be able to do this with the TPL DataFlow library with linking, postponement and release of postponement but I'm not sure what constructs to use.
In pseudocode:
public class Item
{
public string Name { get; set; }
public List<string> DependsOn = new List<string>();
}
ActionBlock<Item> block = null;
var block = new ActionBlock<Item>(o => {
if (!HasActionBlockProcessedAllDependencies(o.DependsOn))
{
// enqueue a callback when ALL dependencies have been completed
}
else
{
DoWork(o);
}
},
new ExecutionDataflowBlockOptions {
MaxDegreeOfParallelism = resourceProcessorOptions.MaximumProviderConcurrency
});
var items = new[]
{
new Item { Name = "Apple", DependsOn = { "Pear" } },
new Item { Name = "Pear" }
}
I am not sure if this will be helpful to you, but here is a custom DependencyTransformBlock class that knows about the dependencies between the items it receives, and processes each one only after its dependencies have been successfully processed. This custom block supports all the built-in functionality of a normal TransformBlock, except from the EnsureOrdered option.
The constructors of this class accept a Func<TInput, TKey> lambda for retrieving the key of each item, and a Func<TInput, IReadOnlyCollection<TKey>> lambda for retrieving its dependencies. The keys are expected to be unique. In case a duplicate key is found, the block will complete with failure.
In case of circular dependencies between items, the affected items will remain unprocessed. The property TInput[] Unprocessed allows to retrieve the unprocessed items after the completion of the block. An item can also remain unprocessed in case any of its dependencies is not supplied.
public class DependencyTransformBlock<TInput, TKey, TOutput> :
ITargetBlock<TInput>, ISourceBlock<TOutput>
{
private readonly ITargetBlock<TInput> _inputBlock;
private readonly IPropagatorBlock<Item, TOutput> _transformBlock;
private readonly object _locker = new object();
private readonly Dictionary<TKey, Item> _items;
private int _pendingCount = 1;
// The initial 1 represents the completion of the _inputBlock
private class Item
{
public TKey Key;
public TInput Input;
public bool HasInput;
public bool IsCompleted;
public HashSet<Item> Dependencies;
public HashSet<Item> Dependents;
public Item(TKey key) => Key = key;
}
public DependencyTransformBlock(
Func<TInput, Task<TOutput>> transform,
Func<TInput, TKey> keySelector,
Func<TInput, IReadOnlyCollection<TKey>> dependenciesSelector,
ExecutionDataflowBlockOptions dataflowBlockOptions = null,
IEqualityComparer<TKey> keyComparer = null)
{
if (transform == null)
throw new ArgumentNullException(nameof(transform));
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (dependenciesSelector == null)
throw new ArgumentNullException(nameof(dependenciesSelector));
dataflowBlockOptions =
dataflowBlockOptions ?? new ExecutionDataflowBlockOptions();
keyComparer = keyComparer ?? EqualityComparer<TKey>.Default;
_items = new Dictionary<TKey, Item>(keyComparer);
_inputBlock = new ActionBlock<TInput>(async input =>
{
var key = keySelector(input);
var dependencyKeys = dependenciesSelector(input);
bool isReadyForProcessing = true;
Item item;
lock (_locker)
{
if (!_items.TryGetValue(key, out item))
{
item = new Item(key);
_items.Add(key, item);
}
if (item.HasInput)
throw new InvalidOperationException($"Duplicate key ({key}).");
item.Input = input;
item.HasInput = true;
if (dependencyKeys != null && dependencyKeys.Count > 0)
{
item.Dependencies = new HashSet<Item>();
foreach (var dependencyKey in dependencyKeys)
{
if (!_items.TryGetValue(dependencyKey, out var dependency))
{
dependency = new Item(dependencyKey);
_items.Add(dependencyKey, dependency);
}
if (!dependency.IsCompleted)
{
item.Dependencies.Add(dependency);
if (dependency.Dependents == null)
dependency.Dependents = new HashSet<Item>();
dependency.Dependents.Add(item);
}
}
isReadyForProcessing = item.Dependencies.Count == 0;
}
if (isReadyForProcessing) _pendingCount++;
}
if (isReadyForProcessing)
{
await _transformBlock.SendAsync(item);
}
}, new ExecutionDataflowBlockOptions()
{
CancellationToken = dataflowBlockOptions.CancellationToken,
BoundedCapacity = 1
});
var middleBuffer = new BufferBlock<Item>(new DataflowBlockOptions()
{
CancellationToken = dataflowBlockOptions.CancellationToken,
BoundedCapacity = DataflowBlockOptions.Unbounded
});
_transformBlock = new TransformBlock<Item, TOutput>(async item =>
{
try
{
TInput input;
lock (_locker)
{
Debug.Assert(item.HasInput && !item.IsCompleted);
input = item.Input;
}
var result = await transform(input).ConfigureAwait(false);
lock (_locker)
{
item.IsCompleted = true;
if (item.Dependents != null)
{
foreach (var dependent in item.Dependents)
{
Debug.Assert(dependent.Dependencies != null);
var removed = dependent.Dependencies.Remove(item);
Debug.Assert(removed);
if (dependent.HasInput
&& dependent.Dependencies.Count == 0)
{
middleBuffer.Post(dependent);
_pendingCount++;
}
}
}
item.Input = default; // Cleanup
item.Dependencies = null;
item.Dependents = null;
}
return result;
}
finally
{
lock (_locker)
{
_pendingCount--;
if (_pendingCount == 0) middleBuffer.Complete();
}
}
}, dataflowBlockOptions);
middleBuffer.LinkTo(_transformBlock);
PropagateCompletion(_inputBlock, middleBuffer,
condition: () => { lock (_locker) return --_pendingCount == 0; });
PropagateCompletion(middleBuffer, _transformBlock);
PropagateFailure(_transformBlock, middleBuffer);
PropagateFailure(_transformBlock, _inputBlock);
}
// Constructor with synchronous lambda
public DependencyTransformBlock(
Func<TInput, TOutput> transform,
Func<TInput, TKey> keySelector,
Func<TInput, IReadOnlyCollection<TKey>> dependenciesSelector,
ExecutionDataflowBlockOptions dataflowBlockOptions = null,
IEqualityComparer<TKey> keyComparer = null) : this(
input => Task.FromResult(transform(input)),
keySelector, dependenciesSelector, dataflowBlockOptions, keyComparer)
{
if (transform == null) throw new ArgumentNullException(nameof(transform));
}
public TInput[] Unprocessed
{
get
{
lock (_locker) return _items.Values
.Where(item => item.HasInput && !item.IsCompleted)
.Select(item => item.Input)
.ToArray();
}
}
public Task Completion => _transformBlock.Completion;
public void Complete() => _inputBlock.Complete();
void IDataflowBlock.Fault(Exception ex) => _inputBlock.Fault(ex);
DataflowMessageStatus ITargetBlock<TInput>.OfferMessage(
DataflowMessageHeader header, TInput value, ISourceBlock<TInput> source,
bool consumeToAccept)
{
return _inputBlock.OfferMessage(header, value, source, consumeToAccept);
}
TOutput ISourceBlock<TOutput>.ConsumeMessage(DataflowMessageHeader header,
ITargetBlock<TOutput> target, out bool messageConsumed)
{
return _transformBlock.ConsumeMessage(header, target, out messageConsumed);
}
bool ISourceBlock<TOutput>.ReserveMessage(DataflowMessageHeader header,
ITargetBlock<TOutput> target)
{
return _transformBlock.ReserveMessage(header, target);
}
void ISourceBlock<TOutput>.ReleaseReservation(DataflowMessageHeader header,
ITargetBlock<TOutput> target)
{
_transformBlock.ReleaseReservation(header, target);
}
public IDisposable LinkTo(ITargetBlock<TOutput> target,
DataflowLinkOptions linkOptions)
{
return _transformBlock.LinkTo(target, linkOptions);
}
private async void PropagateCompletion(IDataflowBlock source,
IDataflowBlock target, Func<bool> condition = null)
{
try { await source.Completion.ConfigureAwait(false); } catch { }
if (source.Completion.IsFaulted)
target.Fault(source.Completion.Exception.InnerException);
else
if (condition == null || condition()) target.Complete();
}
private async void PropagateFailure(IDataflowBlock source,
IDataflowBlock target)
{
try { await source.Completion.ConfigureAwait(false); } catch { }
if (source.Completion.IsFaulted)
target.Fault(source.Completion.Exception.InnerException);
}
}
Usage example:
var block = new DependencyTransformBlock<Item, string, Item>(item =>
{
DoWork(item);
return item;
},
keySelector: item => item.Name,
dependenciesSelector: item => item.DependsOn,
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount
},
keyComparer: StringComparer.OrdinalIgnoreCase);
//...
block.LinkTo(DataflowBlock.NullTarget<Item>());
In this example the block is linked to a NullTarget in order to discard its output, so that it becomes essentially an ActionBlock equivalent.

Automatic dictionary key?

I kept googling for some time, and I found that the best way that enables you to have a list containing variables with a corresponding unique key is a HashTable or a Dictionary, but I didn't find anything that enables you to have automatic keys(of type integer). I want to call a function that adds an object(passed as a parameter) to the dictionary and returns the automatically generated key(int), and without any key duplicates. How could I accomplish this? I am completely struggling!
EDIT: To clarify things up. This is a server, and I want to assign a unique key for each client. If I use the maximum key value, this value will soon get to the int maximum value on large servers. Because if a client connects then disconnects he leaves behind an unused value which should be reused in order to avoid reaching a very high key maximum value.
The following should do and it reuses freed up keys:
internal class AutoKeyDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable
{
private readonly Dictionary<TKey, TValue> inner;
private readonly Func<TKey, TKey> incrementor;
private readonly Stack<TKey> freeKeys;
private readonly TKey keySeed;
private TKey currentKey;
public AutoKeyDictionary(TKey keySeed, Func<TKey, TKey> incrementor)
{
if (keySeed == null)
throw new ArgumentNullException("keySeed");
if (incrementor == null)
throw new ArgumentNullException("incrementor");
inner = new Dictionary<TKey, TValue>();
freeKeys = new Stack<TKey>();
currentKey = keySeed;
}
public TKey Add(TValue value) //returns the used key
{
TKey usedKey;
if (freeKeys.Count > 0)
{
usedKey = freeKeys.Pop();
inner.Add(usedKey, value);
}
else
{
usedKey = currentKey;
inner.Add(usedKey, value);
currentKey = incrementor(currentKey);
}
return usedKey;
}
public void Clear()
{
inner.Clear();
freeKeys.Clear();
currentKey = keySeed;
}
public bool Remove(TKey key)
{
if (inner.Remove(key))
{
if (inner.Count > 0)
{
freeKeys.Push(key);
}
else
{
freeKeys.Clear();
currentKey = keySeed;
}
return true;
}
return false;
}
public bool TryGetValue(TKey key, out TValue value) { return inner.TryGetValue(key, out value); }
public TValue this[TKey key] { get {return inner[key];} set{inner[key] = value;} }
public bool ContainsKey(TKey key) { return inner.ContainsKey(key); }
public bool ContainsValue(TValue value) { return inner.ContainsValue (value); }
public int Count { get{ return inner.Count; } }
public Dictionary<TKey,TValue>.KeyCollection Keys { get { return inner.Keys; } }
public Dictionary<TKey, TValue>.ValueCollection Values { get { return inner.Values; } }
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return inner.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)inner).GetEnumerator(); }
}
Disclaimer: I haven't tested this code, it could have a few pesty bugs of little importance, the general approach is sound.
Write a class which does this. Something like this:
class AutoIndexDictionary : IEnumerable<Whatever>
{
private readonly Dictionary<int, Whatever> myDict = new Dictionary<int, Whatever>();
private int currentIndex = 0;
public int Add(Whatever item)
{
var myIndex = currentIndex
myDict.Add(myIndex, item);
currentIndex ++;
return myIndex;
}
public void Remove(int index)
{
myDict.Remove(index);
}
// implement IEnumerable, indexer etc.
// ...
}
Create a method that gets the max key value from the dictionary using LINQ, adds 1 to it and then uses that as the key for the value you would like to add, like this:
public void AddToMyDictionary(string value)
{
int NextKey = MyDictionary.Keys.Max() + 1;
MyDictionary.Add(NextKey, value);
}
Obviously, this assumes your dictionary is a Dictionary<int, string>, but you can obviously modify for your purposes.
If you want to re-use keys that have been removed, store the next index when something is added / removed.
private int NextKey = 0;
public int AddToMyDictionary(string value)
{
int currentKey = NextKey;
MyDictionary.Add(currentKey, value);
NextKey = MyDictionary.Keys.Max() + 1;
return currentKey;
}
public void RemoveFromMyDictionary(int key)
{
MyDictionary.Remove(key);
NextKey = key;
}
This is what int Object.GetHashCode() is for.
Wouldn't a List do what you say, without any additional overhead? You call it a "unique integer key", but in List terminology, that's simply called an "index".
If you really wanted a custom function to add a value and get a key all in one step, you could inherit from List<T>, like so:
class MyCustomList<T> : List<T>
{
//Not thread-safe
public int AddAndGetKey(T valueToAdd)
{
Add(valueToAdd);
return LastIndexOf(valueToAdd);
}
}
I use LastIndexOf() because the list may include duplicate values and adding to the list always adds to the end. So this should work unless you get into multithreaded situations where you'd have to add-and-get-index in one atomic operation. (Alternately maybe you could add an extension method to List<T>.)
The advantage of using a List is that there would be no gaps in keys. On the flipside, removing an item in the middle would change the key of every item after it. But I guess it depends what behavior you're looking for.
Given the additional information provided in your edit then i don't think int is the correct datatype for you, you shouldn't reuse ID's the way you are describing as if a client with an ID gets disconnected but don't realise then you could have 1 ID in use by 2 clients. change your datatype to Guid then when you get a new client give it a key of Guid.NewGuid() and the chance of duplicate keys drops as close as possible to 0
I like Stefan Steinegger's solution. Here is an alternative that uses a List<> behind the scenes, but ensures the List<> is never removed from:
class AutoKeyDictionary<TValue> : IEnumerable<TValue> where TValue : class
{
readonly List<TValue> list = new List<TValue>();
public int Add(TValue val)
{
if (val == null)
throw new ArgumentNullException(nameof(val), "This collection will not allow null values.");
list.Add(val);
return list.Count - 1;
}
public void RemoveAt(int key)
{
// do not remove ('list.Count' must never decrease), overwrite with null
// (consider throwing if key was already removed)
list[key] = null;
}
public TValue this[int key]
{
get
{
var val = list[key];
if (val == null)
throw new ArgumentOutOfRangeException(nameof(key), "The value with that key is no longer in this collection.");
return val;
}
}
public int NextKey => list.Count;
public int Count => list.Count(v => v != null); // expensive O(n), Linq
public bool ContainsKey(int key) => key >= 0 && key < list.Count && list[key] != null;
public TValue TryGetValue(int key) => (key >= 0 && key < list.Count) ? list[key] : null;
public void Clear()
{
for (var i = 0; i < list.Count; ++i)
list[i] = null;
}
public IEnumerator<TValue> GetEnumerator() => list.Where(v => v != null).GetEnumerator(); // Linq
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int FirstKeyOf(TValue val) => list.IndexOf(val);
public IDictionary<int, TValue> ToDictionary()
{
var retColl = new SortedList<int, TValue>(list.Count);
for (var i = 0; i < list.Count; ++i)
{
var val = list[i];
if (val != null)
retColl.Add(i, val);
}
return retColl;
}
// and so on...
}
Not thread-safe, obviously.
Be aware, the same value can be present several times in the collection, but with different keys.

Enum and IEnumerable in C#

my TIME Enum contains Annual, Monthly, weekly, daily and Hourly.
Here I want to decide which is the minimum and want to return that.
How can I do this ? Here is the code I tried.
private Time DecideMinTime(IEnumerable<Time> g)
{
var minTime = Time.Hourly;
foreach (var element in g)
{
minTime = element;
}
return minTime;
}
Assuming that the numeric value of the enum elements decides what the minimum is:
private Time DecideMinTime(IEnumerable<Time> g)
{
if (g == null) { throw new ArgumentNullException("g"); }
return (Time)g.Cast<int>().Min();
}
If the numeric values indicate the opposite order then you would use .Max() instead of .Min().
As indicated, the numeric order is not consistent. This can be worked around simply by using a mapping indicating the correct order:
static class TimeOrdering
{
private static readonly Dictionary<Time, int> timeOrderingMap;
static TimeOrdering()
{
timeOrderingMap = new Dictionary<Time, int>();
timeOrderingMap[Time.Hourly] = 1;
timeOrderingMap[Time.Daily] = 2;
timeOrderingMap[Time.Weekly] = 3;
timeOrderingMap[Time.Monthly] = 4;
timeOrderingMap[Time.Annual] = 5;
}
public Time DecideMinTime(IEnumerable<Time> g)
{
if (g == null) { throw new ArgumentNullException("g"); }
return g.MinBy(i => timeOrderingMap[i]);
}
public TSource MinBy<TSource, int>(
this IEnumerable<TSource> self,
Func<TSource, int> ordering)
{
if (self == null) { throw new ArgumentNullException("self"); }
if (ordering == null) { throw new ArgumentNullException("ordering"); }
using (var e = self.GetEnumerator()) {
if (!e.MoveNext()) {
throw new ArgumentException("Sequence is empty.", "self");
}
var minElement = e.Current;
var minOrder = ordering(minElement);
while (e.MoveNext()) {
var curOrder = ordering(e.Current);
if (curOrder < minOrder) {
minOrder = curOrder;
minElement = e.Current;
}
}
return minElement;
}
}
}
To make it easier you can assign int values to your enum:
enum Time : byte {Hourly=1, Daily=2, Weekly=3, Monthly=4, Annual=5};
and then
private static Time DecideMinTime(IEnumerable<Time> g)
{
return g.Min();
}
That way you avoid casting back and forth.

Why doesn't Dictionary<TKey, TValue> support null key? [duplicate]

This question already has answers here:
Why can't you use null as a key for a Dictionary<bool?, string>?
(11 answers)
Need an IDictionary<TKey,TValue> implementation that will allow a null key
(8 answers)
Closed last year.
Firstly, why doesn't Dictionary<TKey, TValue> support a single null key?
Secondly, is there an existing dictionary-like collection that does?
I want to store an "empty" or "missing" or "default" System.Type, thought null would work well for this.
More specifically, I've written this class:
class Switch
{
private Dictionary<Type, Action<object>> _dict;
public Switch(params KeyValuePair<Type, Action<object>>[] cases)
{
_dict = new Dictionary<Type, Action<object>>(cases.Length);
foreach (var entry in cases)
_dict.Add(entry.Key, entry.Value);
}
public void Execute(object obj)
{
var type = obj.GetType();
if (_dict.ContainsKey(type))
_dict[type](obj);
}
public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
{
var type = obj.GetType();
foreach (var entry in cases)
{
if (entry.Key == null || type.IsAssignableFrom(entry.Key))
{
entry.Value(obj);
break;
}
}
}
public static KeyValuePair<Type, Action<object>> Case<T>(Action action)
{
return new KeyValuePair<Type, Action<object>>(typeof(T), x => action());
}
public static KeyValuePair<Type, Action<object>> Case<T>(Action<T> action)
{
return new KeyValuePair<Type, Action<object>>(typeof(T), x => action((T)x));
}
public static KeyValuePair<Type, Action<object>> Default(Action action)
{
return new KeyValuePair<Type, Action<object>>(null, x => action());
}
}
For switching on types. There are two ways to use it:
Statically. Just call Switch.Execute(yourObject, Switch.Case<YourType>(x => x.Action()))
Precompiled. Create a switch, and then use it later with switchInstance.Execute(yourObject)
Works great except when you try to add a default case to the "precompiled" version (null argument exception).
Why:
As described before, the problem is that Dictionary requires an implementation of the Object.GetHashCode() method. null does not have an implementation, therefore no hash code associated.
Solution: I have used a solution similar to a NullObject pattern using generics that enables you to use the dictionary seamlessly (no need for a different dictionary implementation).
You can use it like this:
var dict = new Dictionary<NullObject<Type>, string>();
dict[typeof(int)] = "int type";
dict[typeof(string)] = "string type";
dict[null] = "null type";
Assert.AreEqual("int type", dict[typeof(int)]);
Assert.AreEqual("string type", dict[typeof(string)]);
Assert.AreEqual("null type", dict[null]);
You just need to create this struct once in a lifetime :
public struct NullObject<T>
{
[DefaultValue(true)]
private bool isnull;// default property initializers are not supported for structs
private NullObject(T item, bool isnull) : this()
{
this.isnull = isnull;
this.Item = item;
}
public NullObject(T item) : this(item, item == null)
{
}
public static NullObject<T> Null()
{
return new NullObject<T>();
}
public T Item { get; private set; }
public bool IsNull()
{
return this.isnull;
}
public static implicit operator T(NullObject<T> nullObject)
{
return nullObject.Item;
}
public static implicit operator NullObject<T>(T item)
{
return new NullObject<T>(item);
}
public override string ToString()
{
return (Item != null) ? Item.ToString() : "NULL";
}
public override bool Equals(object obj)
{
if (obj == null)
return this.IsNull();
if (!(obj is NullObject<T>))
return false;
var no = (NullObject<T>)obj;
if (this.IsNull())
return no.IsNull();
if (no.IsNull())
return false;
return this.Item.Equals(no.Item);
}
public override int GetHashCode()
{
if (this.isnull)
return 0;
var result = Item.GetHashCode();
if (result >= 0)
result++;
return result;
}
}
It doesn't support it because the dictionary hashes the key to determine the index, which it can't do on a null value.
A quick fix would be to create a dummy class, and insert the key value ?? dummyClassInstance.
Would need more information about what you're actually trying to do to give a less 'hacky' fix
It just hit me that your best answer is probably to just keep track of whether a default case has been defined:
class Switch
{
private Dictionary<Type, Action<object>> _dict;
private Action<object> defaultCase;
public Switch(params KeyValuePair<Type, Action<object>>[] cases)
{
_dict = new Dictionary<Type, Action<object>>(cases.Length);
foreach (var entry in cases)
if (entry.Key == null)
defaultCase = entry.Value;
else
_dict.Add(entry.Key, entry.Value);
}
public void Execute(object obj)
{
var type = obj.GetType();
if (_dict.ContainsKey(type))
_dict[type](obj);
else if (defaultCase != null)
defaultCase(obj);
}
...
The whole rest of your class would remain untouched.
NameValueCollection could take null key.
If you really want a dictionary that allows null keys, here's my quick implementation (not well-written or well-tested):
class NullableDict<K, V> : IDictionary<K, V>
{
Dictionary<K, V> dict = new Dictionary<K, V>();
V nullValue = default(V);
bool hasNull = false;
public NullableDict()
{
}
public void Add(K key, V value)
{
if (key == null)
if (hasNull)
throw new ArgumentException("Duplicate key");
else
{
nullValue = value;
hasNull = true;
}
else
dict.Add(key, value);
}
public bool ContainsKey(K key)
{
if (key == null)
return hasNull;
return dict.ContainsKey(key);
}
public ICollection<K> Keys
{
get
{
if (!hasNull)
return dict.Keys;
List<K> keys = dict.Keys.ToList();
keys.Add(default(K));
return new ReadOnlyCollection<K>(keys);
}
}
public bool Remove(K key)
{
if (key != null)
return dict.Remove(key);
bool oldHasNull = hasNull;
hasNull = false;
return oldHasNull;
}
public bool TryGetValue(K key, out V value)
{
if (key != null)
return dict.TryGetValue(key, out value);
value = hasNull ? nullValue : default(V);
return hasNull;
}
public ICollection<V> Values
{
get
{
if (!hasNull)
return dict.Values;
List<V> values = dict.Values.ToList();
values.Add(nullValue);
return new ReadOnlyCollection<V>(values);
}
}
public V this[K key]
{
get
{
if (key == null)
if (hasNull)
return nullValue;
else
throw new KeyNotFoundException();
else
return dict[key];
}
set
{
if (key == null)
{
nullValue = value;
hasNull = true;
}
else
dict[key] = value;
}
}
public void Add(KeyValuePair<K, V> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
hasNull = false;
dict.Clear();
}
public bool Contains(KeyValuePair<K, V> item)
{
if (item.Key != null)
return ((ICollection<KeyValuePair<K, V>>)dict).Contains(item);
if (hasNull)
return EqualityComparer<V>.Default.Equals(nullValue, item.Value);
return false;
}
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<K, V>>)dict).CopyTo(array, arrayIndex);
if (hasNull)
array[arrayIndex + dict.Count] = new KeyValuePair<K, V>(default(K), nullValue);
}
public int Count
{
get { return dict.Count + (hasNull ? 1 : 0); }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(KeyValuePair<K, V> item)
{
V value;
if (TryGetValue(item.Key, out value) && EqualityComparer<V>.Default.Equals(item.Value, value))
return Remove(item.Key);
return false;
}
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
if (!hasNull)
return dict.GetEnumerator();
else
return GetEnumeratorWithNull();
}
private IEnumerator<KeyValuePair<K, V>> GetEnumeratorWithNull()
{
yield return new KeyValuePair<K, V>(default(K), nullValue);
foreach (var kv in dict)
yield return kv;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
NHibernate comes with a NullableDictionary. That did it for me.
https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Util/NullableDictionary.cs
Dictionary will hash the key supplie to get the index , in case of null , hash function can not return a valid value that's why it does not support null in key.
In your case you are trying to use null as a sentinel value (a "default") instead of actually needing to store null as a value. Rather than go to the hassle of creating a dictionary that can accept null keys, why not just create your own sentinel value. This is a variation on the "null object pattern":
class Switch
{
private class DefaultClass { }
....
public void Execute(object obj)
{
var type = obj.GetType();
Action<object> value;
// first look for actual type
if (_dict.TryGetValue(type, out value) ||
// look for default
_dict.TryGetValue(typeof(DefaultClass), out value))
value(obj);
}
public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
{
var type = obj.GetType();
foreach (var entry in cases)
{
if (entry.Key == typeof(DefaultClass) || type.IsAssignableFrom(entry.Key))
{
entry.Value(obj);
break;
}
}
}
...
public static KeyValuePair<Type, Action<object>> Default(Action action)
{
return new KeyValuePair<Type, Action<object>>(new DefaultClass(), x => action());
}
}
Note that your first Execute function differs significantly from your second. It may be the case that you want something like this:
public void Execute(object obj)
{
Execute(obj, (IEnumerable<KeyValuePair<Type, Action<object>>>)_dict);
}
public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
{
Execute(obj, (IEnumerable<KeyValuePair<Type, Action<object>>>)cases);
}
public static void Execute(object obj, IEnumerable<KeyValuePair<Type, Action<object>>> cases)
{
var type = obj.GetType();
Action<object> defaultEntry = null;
foreach (var entry in cases)
{
if (entry.Key == typeof(DefaultClass))
defaultEntry = entry.Value;
if (type.IsAssignableFrom(entry.Key))
{
entry.Value(obj);
return;
}
}
if (defaultEntry != null)
defaultEntry(obj);
}
I come across this thread some days ago and needed a well thought out and clever solution to handle null keys. I took the time and implemented one by me to handle more scenarios.
You can find my implementation of NullableKeyDictionary currently in my pre-release package Teronis.NetStandard.Collections (0.1.7-alpha.37).
Implementation
public class NullableKeyDictionary<KeyType, ValueType> : INullableKeyDictionary<KeyType, ValueType>, IReadOnlyNullableKeyDictionary<KeyType, ValueType>, IReadOnlyCollection<KeyValuePair<INullableKey<KeyType>, ValueType>> where KeyType : notnull
public interface INullableKeyDictionary<KeyType, ValueType> : IDictionary<KeyType, ValueType>, IDictionary<NullableKey<KeyType>, ValueType> where KeyType : notnull
public interface IReadOnlyNullableKeyDictionary<KeyType, ValueType> : IReadOnlyDictionary<KeyType, ValueType>, IReadOnlyDictionary<NullableKey<KeyType>, ValueType> where KeyType : notnull
Usage (Excerpt of the Xunit test)
// Assign.
var dictionary = new NullableKeyDictionary<string, string>();
IDictionary<string, string> nonNullableDictionary = dictionary;
INullableKeyDictionary<string, string> nullableDictionary = dictionary;
// Assert.
dictionary.Add("value");
/// Assert.Empty does cast to IEnumerable, but our implementation of IEnumerable
/// returns an enumerator of type <see cref="KeyValuePair{NullableKey, TValue}"/>.
/// So we test on correct enumerator implementation wether it can move or not.
Assert.False(nonNullableDictionary.GetEnumerator().MoveNext());
Assert.NotEmpty(nullableDictionary);
Assert.Throws<ArgumentException>(() => dictionary.Add("value"));
Assert.True(dictionary.Remove());
Assert.Empty(nullableDictionary);
dictionary.Add("key", "value");
Assert.True(nonNullableDictionary.GetEnumerator().MoveNext());
Assert.NotEmpty(nullableDictionary);
Assert.Throws<ArgumentException>(() => dictionary.Add("key", "value"));
dictionary.Add("value");
Assert.Equal(1, nonNullableDictionary.Count);
Assert.Equal(2, nullableDictionary.Count);
The following overloads exists for Add(..):
void Add([AllowNull] KeyType key, ValueType value)
void Add(NullableKey<KeyType> key, [AllowNull] ValueType value)
void Add([AllowNull] ValueType value); // Shortcut for adding value with null key.
This class should behave same and intuitive as the dictionary does.
For Remove(..) keys you can use the following overloads:
void Remove([AllowNull] KeyType key)
void Remove(NullableKey<KeyType> key)
void Remove(); // Shortcut for removing value with null key.
The indexers do accept [AllowNull] KeyType or NullableKey<KeyType>. So supported scenarios, like they are stated in other posts, are supported:
var dict = new NullableKeyDictionary<Type, string>
dict[typeof(int)] = "int type";
dict[typeof(string)] = "string type";
dict[null] = "null type";
// Or:
dict[NullableKey<Type>.Null] = "null type";
I highly appreciate feedback and suggestions for improvements. :)
EDIT: Real answer to the question actually being asked: Why can't you use null as a key for a Dictionary<bool?, string>?
The reason the generic dictionary doesn't support null is because TKey might be a value type, which doesn't have null.
new Dictionary<int, string>[null] = "Null"; //error!
To get one that does, you could either use the non-generic Hashtable (which uses object keys and values), or roll your own with DictionaryBase.
Edit: just to clarify why null is illegal in this case, consider this generic method:
bool IsNull<T> (T value) {
return value == null;
}
But what happens when you call IsNull<int>(null)?
Argument '1': cannot convert from '<null>' to 'int'
You get a compiler error, since you can't convert null to an int. We can fix it, by saying that we only want nullable types:
bool IsNull<T> (T value) where T : class {
return value == null;
}
And, that's A-Okay. The restriction is that we can no longer call IsNull<int>, since int is not a class (nullable object)

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