Introduction
This article presents an OrderedDictionary<TKey,TValue>
you can use in your projects since the Microsoft's OrderedDictionary
class doesn't have a generic counterpart. This is a rewrite of the ordered dictionary rather than a wrapper to avoid boxing and unboxing.
Background
Standard IDictionary<TKey,TValue>
containers aren't guaranteed to preserve the order of items. Sometimes however, you need a container that allows you to address by key or by index. That is what this class is for.
Using the Code
The code is partitioned into sections via partial
classes in order to make it easier to read and navigate. The following is the core of the ordered dictionary, in OrderedDictionary.cs:
public partial class OrderedDictionary<TKey,TValue>
{
List<KeyValuePair<TKey,TValue>> _innerList;
Dictionary<TKey, TValue> _innerDictionary;
IEqualityComparer<TKey> _comparer = null;
public OrderedDictionary(int capacity,IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity, comparer);
_innerList = new List<KeyValuePair<TKey,TValue>>(capacity);
_comparer = comparer;
}
public OrderedDictionary(IEnumerable<KeyValuePair<TKey,TValue>> collection,
IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(comparer);
_innerList = new List<KeyValuePair<TKey, TValue>>();
_AddValues(collection);
_comparer = comparer;
}
public OrderedDictionary(int capacity)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity);
_innerList = new List<KeyValuePair<TKey, TValue>>(capacity);
}
public OrderedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
{
_innerDictionary = new Dictionary<TKey, TValue>();
_innerList = new List<KeyValuePair<TKey, TValue>>();
_AddValues(collection);
}
public OrderedDictionary(IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(comparer);
_innerList = new List<KeyValuePair<TKey, TValue>>();
_comparer = comparer;
}
public OrderedDictionary()
{
_innerDictionary = new Dictionary<TKey, TValue>();
_innerList = new List<KeyValuePair<TKey, TValue>>();
}
public TValue GetAt(int index)
{
return _innerList[index].Value;
}
public void SetAt(int index,TValue value)
{
var key = _innerList[index].Key;
_innerList[index] = new KeyValuePair<TKey, TValue>(key, value);
_innerDictionary[key] = value;
}
public void Insert(int index, TKey key, TValue value)
=> (this as IList<KeyValuePair<TKey, TValue>>).Insert
(index, new KeyValuePair<TKey, TValue>(key, value));
void _AddValues(IEnumerable<KeyValuePair<TKey,TValue>> collection)
{
foreach (var kvp in collection)
{
_innerDictionary.Add(kvp.Key, kvp.Value);
_innerList.Add(kvp);
}
}
}
As you can see, this class implements the standard Dictionary<TKey,TValue>
constructors. Using them is exactly the same. It also has several extra methods like GetAt()
and SetAt()
. These and the rest allow for indexing into the dictionary. The dictionary also implements a list interface - IList<KeyValuePair<TKey,TValue>>
which allows you to treat the entire container like an indexed list of key/value pairs. Of course, it also implements the standard dictionary interfaces.
All lookups are hashtable lookups or indexed lookups. This means they are fast. The add, insert, set, and removal methods are slightly slower than a standard dictionary as a result of having to keep both containers in sync.
Using it is demonstrated in the demo program. Enjoy!
Points of Interest
In order to fulfill all of the operations, we require two copies of every TKey
and TValue
entry - one set in the list, and the other in the dictionary. This is required. There is no other way to efficiently provide all of the operations without mirroring all of the data between both containers. The trade off is more speed at the cost of more memory.
History
- 20th November, 2019 - Initial submission