Related
I am writing a custom observable collection for some of my needs and I want it to allow EditItem when used within a WPF DataGrid.
(see the code of the collection at the end of the question to avoid polluting the reading of the question)
You will see that I thought of 3 solutions. But I cannot get any of them working for now.
Solution 1
According to this question, if I want my ObservableDictionary to allow EditItem, it has to implement the non-generic IList. But as I am using a IDictionary as backing field to store the elements, it is impossible to properly implement the IList interface (because it is based on ordered index).
Unless someone find a way?
Solution 2
Next idea, instead of letting the system chose the CollectionView implementation, I can force it to use my own, like this:
<CollectionViewSource
x:Key="FooesSource"
Source="{Binding Fooes}"
CollectionViewType="local:MyCollectionView" />
To do so, I tried to override one existing CollectionView, one allowing EditItem, to suit my needs. For example:
class ObservableDictionaryCollectionView : ListCollectionView
{
public ObservableDictionaryCollectionView(IDictionary dictionary)
: base(dictionary.Values)
{
}
}
But it does not work because dictionary.Values is a ICollection and ICollection does not implement IList (it is the opposite -_-).
Is there another built-in CollectionView that could suit my needs?
Solution 3
Based on the next idea, I could try to write my own, from scratch, CollectionView. But before doing this, I would like to know:
If anyone has a better idea?
If it is possible?
May be some dictionary constraints made this impossible? Which interfaces should I implement? (IEditableCollectionView, IEditableCollectionViewAddNewItem, IEditableCollectionView, ICollectionViewLiveShaping, etc)
As promised, here is the code of the collection:
public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IEnumerable<TValue>, INotifyCollectionChanged
{
#region fields
private IDictionary<TKey, TValue> _innerDictionary;
#endregion
#region properties
public int Count { get { return _innerDictionary.Count; } }
public ICollection<TKey> Keys { get { return _innerDictionary.Keys; } }
public ICollection<TValue> Values { get { return _innerDictionary.Values; } }
public bool IsReadOnly { get { return false; } }
#endregion
#region indexors
public TValue this[TKey key]
{
get { return _innerDictionary[key]; }
set { this.InternalAdd(new KeyValuePair<TKey, TValue>(key, value)); }
}
#endregion
#region events
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region constructors
public ObservableDictionary()
{
_innerDictionary = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(int capacity)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity);
}
public ObservableDictionary(IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(comparer);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_innerDictionary = new Dictionary<TKey, TValue>(dictionary);
}
public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
#endregion
#region public methods
public bool ContainsKey(TKey key)
{
return _innerDictionary.ContainsKey(key);
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _innerDictionary.Contains(item);
}
public void Add(TKey key, TValue value)
{
this.InternalAdd(new KeyValuePair<TKey, TValue>(key, value));
}
public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> items)
{
if (!items.Any())
{
return;
}
var added = new List<TValue>();
var removed = new List<TValue>();
foreach (var item in items)
{
TValue value;
if (_innerDictionary.TryGetValue(item.Key, out value))
{
removed.Add(value);
}
added.Add(item.Value);
_innerDictionary[item.Key] = item.Value;
}
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, null));
if (removed.Count > 0)
{
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, removed));
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this.InternalAdd(item);
}
public bool TryGetValue(TKey key, out TValue value)
{
return _innerDictionary.TryGetValue(key, out value);
}
public bool Remove(TKey key)
{
return this.InternalRemove(key);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return this.InternalRemove(item.Key);
}
public void Clear()
{
_innerDictionary.Clear();
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_innerDictionary.CopyTo(array, arrayIndex);
}
public IEnumerator<TValue> GetEnumerator()
{
return Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return _innerDictionary.GetEnumerator();
}
#endregion
#region private methods
/// <summary>
/// Adds the specified value to the internal dictionary and indicates whether the element has actually been added. Fires the CollectionChanged event accordingly.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private void InternalAdd(KeyValuePair<TKey, TValue> item)
{
IList added = new TValue[] { item.Value };
TValue value;
if (_innerDictionary.TryGetValue(item.Key, out value))
{
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, new TValue[] { value }));
}
_innerDictionary[item.Key] = item.Value;
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, null));
}
/// <summary>
/// Remove the specified key from the internal dictionary and indicates whether the element has actually been removed. Fires the CollectionChanged event accordingly.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private bool InternalRemove(TKey key)
{
TValue value;
if (_innerDictionary.TryGetValue(key, out value))
{
_innerDictionary.Remove(key);
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, new TValue[] { value }));
}
return value != null;
}
#endregion
}
You could build an ObservableCollection that implements both Dictionary and List, by doubly storing the inserted data into an internal list and a dictionary. The Dictionary provides the advantages of a dictionary, the List providing the better support for Binding Operations.
Disadvantages include doubling up your memory usage, and hurting your item removal time*
* Inserts** and Accesses have the same efficiency as a regular dictionary, but removal has to find the item in the internal list as well
** I guess inserts can also be more costly on the occasions they require array resizes.
I am using Entity Framework V6, WCF, and Windowsforms:
I am trying to map out a 1D object value to property of an Collection of MyObjectDTO which makes it a 3D (maybe that is 2D) object space (Defined basically as Rows, Columns ) Datatables came to my mind but entity framework has entities not tables!
I have an array of Items[1..n] returned to me from a 3rd party component and each element of that array represents a property in my DTO and can be 1 or even 2000 items.
I am trying to map that Items[x] to a property of a given DTO in collection KeyedList
The Items[n] has an object KeyID which I defined as a class called KeyID with 3 properties Parent, UniqueID, PropertyName - which I set when it is instantiated - for its lifetime (basically as long as my application will run with out crashing) it must remain the same!
I have the following code but I am not sure if this is the correct approach, the long approach or the wrong approach.
So I would like some serious experts to look at the code and tell me if there is a better way or a simpler way to do this.
// This is only one of the DTO's could be different DTO's
class MyDTOobject
{
bool Property1 {get; set;}
bool Property2 {get; set;}
int Property3 {get; set;}
string Property4 {get; set;}
float Property5 {get; set;}
string Property6 {get; set;}
}
KeyID
{
Guid Parent {get; set;}
Guid UniqueID {get; set;}
string PropertyName {get; set;}
}
KeyedList<MyDTO> MyDTOList KeyedList<MyDTO>
// ItemValues[] are 1 to n not always the same or the same items.
OtherGuysObjectsChanged(ItemValues[] items)
{
for(int i=0; i < items.Length; i++)
{
// this an object items.KeyID
keyID = (KeyID)items[i].KeyID;
MyInstanceDTOobject = MyDTOList[keyID.Parent];
if(MyInstanceDTOobject != null)
{
var setvalues = SetValues[items[i].KeyID.PropertyName];
setvalues(MyInstanceDTOobject, items[i].Value);
MyDTOList[keyID.Parent] = MyInstanceDTOobject;
}
}
// Call to thread to push this stuff to WCF Clients.
// Maybe even every .250 seconds would like to transform my list into simple list if I need to .
// Call to thread for my entityFramework stuff and take that list of DTO's
// that has now changed and update a db .
}
Dictionary<string,Action<MyDTOobject, object> SetValues = new Dictionary<string,Action<MyDTOobject, object>()
{
{"Property1", (m,v) => m.Property1 = (bool)v.value},
{"Property2", (m,v) => m.Property2 = (bool)v.value},
{"Property3", (m,v) => m.Property3 = (int)v.value},
{"Property4", (m,v) => m.Property4 = (string)v.value},
{"Property5", (m,v) => m.Property5 = (float)v.value},
{"Property6", (m,v) => m.Property6 = (string)v.value} ;
}
// KeyedList<T>
// Basically I found an IList implementation on the web and Modified it with the addition of a dictionary
//- I am hoping to be able push this list to a WCF client and update a DataGridview with it.
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
namespace Utility.Collections
{
public class KeyedList<T> : IList<T>
{
internal List<T> _innerList;
private List<T> InnerList
{
get
{
return _innerList;
}
set
{
_innerList = value;
OnInnerListChanged(EventArgs.Empty);
}
}
public event EventHandler InnerListChanged;
/// <summary>
/// Inner List Changed Event
/// </summary>
/// <param name="e"></param>
protected virtual void OnInnerListChanged(EventArgs e)
{
var handler = InnerListChanged;
if (handler != null)
{
handler(this, e);
}
}
private IEnumerable<T> _keyedLoader;
internal IEnumerable<T> KeyedLoader
{
get
{
return _keyedLoader;
}
set
{
_keyedLoader = value;
}
}
[IgnoreDataMember]
[NonSerialized]
private Dictionary<object, int> _itemLookup;
[IgnoreDataMember]
internal Dictionary<object, int> ItemLookup
{
get
{
return _itemLookup;
}
set
{
_itemLookup = value;
}
}
/// <summary>
///
/// </summary>
private void ensureList()
{
if(InnerList == null)
{
InnerList = new List<T>(KeyedLoader);
//_itemLookup = new Dictionary<object, int>();
}
if(ItemLookup == null)
{
_itemLookup = new Dictionary<object, int>();
}
}
/// <summary>
///
/// </summary>
private void RefreshDictionary()
{
ensureList();
for (int i = 0; i < this.ItemLookup.Count; i++)
{
ItemLookup = new Dictionary<object, int>();
ItemLookup[InnerList[i]] = i;
}
}
#region IList<T> Members
public int IndexOf(T item)
{
ensureList();
return InnerList.IndexOf(item);
}
public void Insert(int index, T item)
{
ensureList();
InnerList.Insert(index, item);
RefreshDictionary();
}
public void RemoveAt(int index)
{
ensureList();
InnerList.RemoveAt(index);
RefreshDictionary();
}
public T this[int index]
{
get
{
ensureList();
return InnerList[index];
}
set
{
ensureList();
InnerList[index] = value;
ItemLookup[value] = index;
}
}
/// <summary>
/// ?Will throw an exception if the Key does not exist!
/// </summary>
public T this[object key]
{
get
{
ensureList();
return InnerList[ItemLookup[key]];
}
set
{
ensureList();
if (ItemLookup.ContainsKey(value))
{
InnerList[ItemLookup[key]] = value;
}
else
{
InnerList.Add( (T)Activator.CreateInstance( typeof(T) ) );
ItemLookup[key] = InnerList.Count-1;
// throw new ArgumentOutOfRangeException(string.Format("{0}", "Key"),
// "Key is not found in the dictionary cannot set value");
}
}
}
#endregion
#region ICollection<T> Members
public void Add(T item)
{
ensureList();
InnerList.Add(item);
}
public void Clear()
{
ensureList();
InnerList.Clear();
}
public bool Contains(T item)
{
ensureList();
return InnerList.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
ensureList();
InnerList.CopyTo(array, arrayIndex);
}
public int Count
{
get { ensureList(); return InnerList.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
ensureList();
bool retVal = InnerList.Remove(item);
RefreshDictionary();
return retVal;
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
ensureList();
return InnerList.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
ensureList();
return InnerList.GetEnumerator();
}
#endregion
}
}
Do two objects have the same properties? If so, I recommend to you to use AutoMapper. It eliminates manual and tedious mapping from DTO to other models. You can install it using nuget by typing Install-Package AutoMapper in nuget package manager.
I am looking for a generic, bidirectional 1 to 1 Dictionary class in C# (2), ie. a BiDictionaryOneToOne<T, S> which is guaranteed to only contain one of each value and key (up to RefEquals anyway), and which can be searched using either key or value. Anyone know of one, or should I just implement it myself? I can't believe that I'm the first person to need this...
There is a BiDictionary in the answers to this question, but it is not for unique elements (and also does not implement RemoveByFirst(T t) or RemoveBySecond(S s)).
Thanks!
OK, here is my attempt (building on Jon's - thanks), archived here and open for improvement :
/// <summary>
/// This is a dictionary guaranteed to have only one of each value and key.
/// It may be searched either by TFirst or by TSecond, giving a unique answer because it is 1 to 1.
/// </summary>
/// <typeparam name="TFirst">The type of the "key"</typeparam>
/// <typeparam name="TSecond">The type of the "value"</typeparam>
public class BiDictionaryOneToOne<TFirst, TSecond>
{
IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();
#region Exception throwing methods
/// <summary>
/// Tries to add the pair to the dictionary.
/// Throws an exception if either element is already in the dictionary
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
public void Add(TFirst first, TSecond second)
{
if (firstToSecond.ContainsKey(first) || secondToFirst.ContainsKey(second))
throw new ArgumentException("Duplicate first or second");
firstToSecond.Add(first, second);
secondToFirst.Add(second, first);
}
/// <summary>
/// Find the TSecond corresponding to the TFirst first
/// Throws an exception if first is not in the dictionary.
/// </summary>
/// <param name="first">the key to search for</param>
/// <returns>the value corresponding to first</returns>
public TSecond GetByFirst(TFirst first)
{
TSecond second;
if (!firstToSecond.TryGetValue(first, out second))
throw new ArgumentException("first");
return second;
}
/// <summary>
/// Find the TFirst corresponing to the Second second.
/// Throws an exception if second is not in the dictionary.
/// </summary>
/// <param name="second">the key to search for</param>
/// <returns>the value corresponding to second</returns>
public TFirst GetBySecond(TSecond second)
{
TFirst first;
if (!secondToFirst.TryGetValue(second, out first))
throw new ArgumentException("second");
return first;
}
/// <summary>
/// Remove the record containing first.
/// If first is not in the dictionary, throws an Exception.
/// </summary>
/// <param name="first">the key of the record to delete</param>
public void RemoveByFirst(TFirst first)
{
TSecond second;
if (!firstToSecond.TryGetValue(first, out second))
throw new ArgumentException("first");
firstToSecond.Remove(first);
secondToFirst.Remove(second);
}
/// <summary>
/// Remove the record containing second.
/// If second is not in the dictionary, throws an Exception.
/// </summary>
/// <param name="second">the key of the record to delete</param>
public void RemoveBySecond(TSecond second)
{
TFirst first;
if (!secondToFirst.TryGetValue(second, out first))
throw new ArgumentException("second");
secondToFirst.Remove(second);
firstToSecond.Remove(first);
}
#endregion
#region Try methods
/// <summary>
/// Tries to add the pair to the dictionary.
/// Returns false if either element is already in the dictionary
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns>true if successfully added, false if either element are already in the dictionary</returns>
public Boolean TryAdd(TFirst first, TSecond second)
{
if (firstToSecond.ContainsKey(first) || secondToFirst.ContainsKey(second))
return false;
firstToSecond.Add(first, second);
secondToFirst.Add(second, first);
return true;
}
/// <summary>
/// Find the TSecond corresponding to the TFirst first.
/// Returns false if first is not in the dictionary.
/// </summary>
/// <param name="first">the key to search for</param>
/// <param name="second">the corresponding value</param>
/// <returns>true if first is in the dictionary, false otherwise</returns>
public Boolean TryGetByFirst(TFirst first, out TSecond second)
{
return firstToSecond.TryGetValue(first, out second);
}
/// <summary>
/// Find the TFirst corresponding to the TSecond second.
/// Returns false if second is not in the dictionary.
/// </summary>
/// <param name="second">the key to search for</param>
/// <param name="first">the corresponding value</param>
/// <returns>true if second is in the dictionary, false otherwise</returns>
public Boolean TryGetBySecond(TSecond second, out TFirst first)
{
return secondToFirst.TryGetValue(second, out first);
}
/// <summary>
/// Remove the record containing first, if there is one.
/// </summary>
/// <param name="first"></param>
/// <returns> If first is not in the dictionary, returns false, otherwise true</returns>
public Boolean TryRemoveByFirst(TFirst first)
{
TSecond second;
if (!firstToSecond.TryGetValue(first, out second))
return false;
firstToSecond.Remove(first);
secondToFirst.Remove(second);
return true;
}
/// <summary>
/// Remove the record containing second, if there is one.
/// </summary>
/// <param name="second"></param>
/// <returns> If second is not in the dictionary, returns false, otherwise true</returns>
public Boolean TryRemoveBySecond(TSecond second)
{
TFirst first;
if (!secondToFirst.TryGetValue(second, out first))
return false;
secondToFirst.Remove(second);
firstToSecond.Remove(first);
return true;
}
#endregion
/// <summary>
/// The number of pairs stored in the dictionary
/// </summary>
public Int32 Count
{
get { return firstToSecond.Count; }
}
/// <summary>
/// Removes all items from the dictionary.
/// </summary>
public void Clear()
{
firstToSecond.Clear();
secondToFirst.Clear();
}
}
A more complete implementation of bidirectional dictionary:
Supports almost all interfaces of original Dictionary<TKey,TValue> (except infrastructure interfaces):
IDictionary<TKey, TValue>
IReadOnlyDictionary<TKey, TValue>
IDictionary
ICollection<KeyValuePair<TKey, TValue>> (this one and below are the base interfaces of the ones above)
ICollection
IReadOnlyCollection<KeyValuePair<TKey, TValue>>
IEnumerable<KeyValuePair<TKey, TValue>>
IEnumerable
Serialization using SerializableAttribute.
Debug view using DebuggerDisplayAttribute (with Count info) and DebuggerTypeProxyAttribute (for displaying key-value pairs in watches).
Reverse dictionary is available as IDictionary<TValue, TKey> Reverse property and also implements all interfaces mentioned above. All operations on either dictionaries modify both.
Usage:
var dic = new BiDictionary<int, string>();
dic.Add(1, "1");
dic[2] = "2";
dic.Reverse.Add("3", 3);
dic.Reverse["4"] = 4;
dic.Clear();
Code is available in my private framework on GitHub: BiDictionary(TFirst,TSecond).cs (permalink, search).
Copy:
[Serializable]
[DebuggerDisplay ("Count = {Count}"), DebuggerTypeProxy (typeof(DictionaryDebugView<,>))]
public class BiDictionary<TFirst, TSecond> : IDictionary<TFirst, TSecond>, IReadOnlyDictionary<TFirst, TSecond>, IDictionary
{
private readonly IDictionary<TFirst, TSecond> _firstToSecond = new Dictionary<TFirst, TSecond>();
[NonSerialized]
private readonly IDictionary<TSecond, TFirst> _secondToFirst = new Dictionary<TSecond, TFirst>();
[NonSerialized]
private readonly ReverseDictionary _reverseDictionary;
public BiDictionary ()
{
_reverseDictionary = new ReverseDictionary(this);
}
public IDictionary<TSecond, TFirst> Reverse
{
get { return _reverseDictionary; }
}
public int Count
{
get { return _firstToSecond.Count; }
}
object ICollection.SyncRoot
{
get { return ((ICollection)_firstToSecond).SyncRoot; }
}
bool ICollection.IsSynchronized
{
get { return ((ICollection)_firstToSecond).IsSynchronized; }
}
bool IDictionary.IsFixedSize
{
get { return ((IDictionary)_firstToSecond).IsFixedSize; }
}
public bool IsReadOnly
{
get { return _firstToSecond.IsReadOnly || _secondToFirst.IsReadOnly; }
}
public TSecond this [TFirst key]
{
get { return _firstToSecond[key]; }
set
{
_firstToSecond[key] = value;
_secondToFirst[value] = key;
}
}
object IDictionary.this [object key]
{
get { return ((IDictionary)_firstToSecond)[key]; }
set
{
((IDictionary)_firstToSecond)[key] = value;
((IDictionary)_secondToFirst)[value] = key;
}
}
public ICollection<TFirst> Keys
{
get { return _firstToSecond.Keys; }
}
ICollection IDictionary.Keys
{
get { return ((IDictionary)_firstToSecond).Keys; }
}
IEnumerable<TFirst> IReadOnlyDictionary<TFirst, TSecond>.Keys
{
get { return ((IReadOnlyDictionary<TFirst, TSecond>)_firstToSecond).Keys; }
}
public ICollection<TSecond> Values
{
get { return _firstToSecond.Values; }
}
ICollection IDictionary.Values
{
get { return ((IDictionary)_firstToSecond).Values; }
}
IEnumerable<TSecond> IReadOnlyDictionary<TFirst, TSecond>.Values
{
get { return ((IReadOnlyDictionary<TFirst, TSecond>)_firstToSecond).Values; }
}
public IEnumerator<KeyValuePair<TFirst, TSecond>> GetEnumerator ()
{
return _firstToSecond.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator ()
{
return GetEnumerator();
}
IDictionaryEnumerator IDictionary.GetEnumerator ()
{
return ((IDictionary)_firstToSecond).GetEnumerator();
}
public void Add (TFirst key, TSecond value)
{
_firstToSecond.Add(key, value);
_secondToFirst.Add(value, key);
}
void IDictionary.Add (object key, object value)
{
((IDictionary)_firstToSecond).Add(key, value);
((IDictionary)_secondToFirst).Add(value, key);
}
public void Add (KeyValuePair<TFirst, TSecond> item)
{
_firstToSecond.Add(item);
_secondToFirst.Add(item.Reverse());
}
public bool ContainsKey (TFirst key)
{
return _firstToSecond.ContainsKey(key);
}
public bool Contains (KeyValuePair<TFirst, TSecond> item)
{
return _firstToSecond.Contains(item);
}
public bool TryGetValue (TFirst key, out TSecond value)
{
return _firstToSecond.TryGetValue(key, out value);
}
public bool Remove (TFirst key)
{
TSecond value;
if (_firstToSecond.TryGetValue(key, out value)) {
_firstToSecond.Remove(key);
_secondToFirst.Remove(value);
return true;
}
else
return false;
}
void IDictionary.Remove (object key)
{
var firstToSecond = (IDictionary)_firstToSecond;
if (!firstToSecond.Contains(key))
return;
var value = firstToSecond[key];
firstToSecond.Remove(key);
((IDictionary)_secondToFirst).Remove(value);
}
public bool Remove (KeyValuePair<TFirst, TSecond> item)
{
return _firstToSecond.Remove(item);
}
public bool Contains (object key)
{
return ((IDictionary)_firstToSecond).Contains(key);
}
public void Clear ()
{
_firstToSecond.Clear();
_secondToFirst.Clear();
}
public void CopyTo (KeyValuePair<TFirst, TSecond>[] array, int arrayIndex)
{
_firstToSecond.CopyTo(array, arrayIndex);
}
void ICollection.CopyTo (Array array, int index)
{
((IDictionary)_firstToSecond).CopyTo(array, index);
}
[OnDeserialized]
internal void OnDeserialized (StreamingContext context)
{
_secondToFirst.Clear();
foreach (var item in _firstToSecond)
_secondToFirst.Add(item.Value, item.Key);
}
private class ReverseDictionary : IDictionary<TSecond, TFirst>, IReadOnlyDictionary<TSecond, TFirst>, IDictionary
{
private readonly BiDictionary<TFirst, TSecond> _owner;
public ReverseDictionary (BiDictionary<TFirst, TSecond> owner)
{
_owner = owner;
}
public int Count
{
get { return _owner._secondToFirst.Count; }
}
object ICollection.SyncRoot
{
get { return ((ICollection)_owner._secondToFirst).SyncRoot; }
}
bool ICollection.IsSynchronized
{
get { return ((ICollection)_owner._secondToFirst).IsSynchronized; }
}
bool IDictionary.IsFixedSize
{
get { return ((IDictionary)_owner._secondToFirst).IsFixedSize; }
}
public bool IsReadOnly
{
get { return _owner._secondToFirst.IsReadOnly || _owner._firstToSecond.IsReadOnly; }
}
public TFirst this [TSecond key]
{
get { return _owner._secondToFirst[key]; }
set
{
_owner._secondToFirst[key] = value;
_owner._firstToSecond[value] = key;
}
}
object IDictionary.this [object key]
{
get { return ((IDictionary)_owner._secondToFirst)[key]; }
set
{
((IDictionary)_owner._secondToFirst)[key] = value;
((IDictionary)_owner._firstToSecond)[value] = key;
}
}
public ICollection<TSecond> Keys
{
get { return _owner._secondToFirst.Keys; }
}
ICollection IDictionary.Keys
{
get { return ((IDictionary)_owner._secondToFirst).Keys; }
}
IEnumerable<TSecond> IReadOnlyDictionary<TSecond, TFirst>.Keys
{
get { return ((IReadOnlyDictionary<TSecond, TFirst>)_owner._secondToFirst).Keys; }
}
public ICollection<TFirst> Values
{
get { return _owner._secondToFirst.Values; }
}
ICollection IDictionary.Values
{
get { return ((IDictionary)_owner._secondToFirst).Values; }
}
IEnumerable<TFirst> IReadOnlyDictionary<TSecond, TFirst>.Values
{
get { return ((IReadOnlyDictionary<TSecond, TFirst>)_owner._secondToFirst).Values; }
}
public IEnumerator<KeyValuePair<TSecond, TFirst>> GetEnumerator ()
{
return _owner._secondToFirst.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator ()
{
return GetEnumerator();
}
IDictionaryEnumerator IDictionary.GetEnumerator ()
{
return ((IDictionary)_owner._secondToFirst).GetEnumerator();
}
public void Add (TSecond key, TFirst value)
{
_owner._secondToFirst.Add(key, value);
_owner._firstToSecond.Add(value, key);
}
void IDictionary.Add (object key, object value)
{
((IDictionary)_owner._secondToFirst).Add(key, value);
((IDictionary)_owner._firstToSecond).Add(value, key);
}
public void Add (KeyValuePair<TSecond, TFirst> item)
{
_owner._secondToFirst.Add(item);
_owner._firstToSecond.Add(item.Reverse());
}
public bool ContainsKey (TSecond key)
{
return _owner._secondToFirst.ContainsKey(key);
}
public bool Contains (KeyValuePair<TSecond, TFirst> item)
{
return _owner._secondToFirst.Contains(item);
}
public bool TryGetValue (TSecond key, out TFirst value)
{
return _owner._secondToFirst.TryGetValue(key, out value);
}
public bool Remove (TSecond key)
{
TFirst value;
if (_owner._secondToFirst.TryGetValue(key, out value)) {
_owner._secondToFirst.Remove(key);
_owner._firstToSecond.Remove(value);
return true;
}
else
return false;
}
void IDictionary.Remove (object key)
{
var firstToSecond = (IDictionary)_owner._secondToFirst;
if (!firstToSecond.Contains(key))
return;
var value = firstToSecond[key];
firstToSecond.Remove(key);
((IDictionary)_owner._firstToSecond).Remove(value);
}
public bool Remove (KeyValuePair<TSecond, TFirst> item)
{
return _owner._secondToFirst.Remove(item);
}
public bool Contains (object key)
{
return ((IDictionary)_owner._secondToFirst).Contains(key);
}
public void Clear ()
{
_owner._secondToFirst.Clear();
_owner._firstToSecond.Clear();
}
public void CopyTo (KeyValuePair<TSecond, TFirst>[] array, int arrayIndex)
{
_owner._secondToFirst.CopyTo(array, arrayIndex);
}
void ICollection.CopyTo (Array array, int index)
{
((IDictionary)_owner._secondToFirst).CopyTo(array, index);
}
}
}
internal class DictionaryDebugView<TKey, TValue>
{
private readonly IDictionary<TKey, TValue> _dictionary;
[DebuggerBrowsable (DebuggerBrowsableState.RootHidden)]
public KeyValuePair<TKey, TValue>[] Items
{
get
{
var array = new KeyValuePair<TKey, TValue>[_dictionary.Count];
_dictionary.CopyTo(array, 0);
return array;
}
}
public DictionaryDebugView (IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
}
}
public static class KeyValuePairExts
{
public static KeyValuePair<TValue, TKey> Reverse<TKey, TValue> (this KeyValuePair<TKey, TValue> #this)
{
return new KeyValuePair<TValue, TKey>(#this.Value, #this.Key);
}
}
The question you refer to also shows a one-to-one implementation in this answer. Adding RemoveByFirst and RemoveBySecond would be trivial - as would implementing extra interfaces etc.
This is same as accepted answer, but I provided Update methods as well, and over all little more fleshed out:
public class BiDictionary<TKey1, TKey2> : IEnumerable<Tuple<TKey1, TKey2>>
{
Dictionary<TKey1, TKey2> _forwards;
Dictionary<TKey2, TKey1> _reverses;
public int Count
{
get
{
if (_forwards.Count != _reverses.Count)
throw new Exception("somewhere logic went wrong and your data got corrupt");
return _forwards.Count;
}
}
public ICollection<TKey1> Key1s
{
get { return _forwards.Keys; }
}
public ICollection<TKey2> Key2s
{
get { return _reverses.Keys; }
}
public BiDictionary(IEqualityComparer<TKey1> comparer1 = null, IEqualityComparer<TKey2> comparer2 = null)
{
_forwards = new Dictionary<TKey1, TKey2>(comparer1);
_reverses = new Dictionary<TKey2, TKey1>(comparer2);
}
public bool ContainsKey1(TKey1 key)
{
return ContainsKey(key, _forwards);
}
private static bool ContainsKey<S, T>(S key, Dictionary<S, T> dict)
{
return dict.ContainsKey(key);
}
public bool ContainsKey2(TKey2 key)
{
return ContainsKey(key, _reverses);
}
public TKey2 GetValueByKey1(TKey1 key)
{
return GetValueByKey(key, _forwards);
}
private static T GetValueByKey<S, T>(S key, Dictionary<S, T> dict)
{
return dict[key];
}
public TKey1 GetValueByKey2(TKey2 key)
{
return GetValueByKey(key, _reverses);
}
public bool TryGetValueByKey1(TKey1 key, out TKey2 value)
{
return TryGetValue(key, _forwards, out value);
}
private static bool TryGetValue<S, T>(S key, Dictionary<S, T> dict, out T value)
{
return dict.TryGetValue(key, out value);
}
public bool TryGetValueByKey2(TKey2 key, out TKey1 value)
{
return TryGetValue(key, _reverses, out value);
}
public bool Add(TKey1 key1, TKey2 key2)
{
if (ContainsKey1(key1) || ContainsKey2(key2)) // very important
return false;
AddOrUpdate(key1, key2);
return true;
}
public void AddOrUpdateByKey1(TKey1 key1, TKey2 key2)
{
if (!UpdateByKey1(key1, key2))
AddOrUpdate(key1, key2);
}
// dont make this public; a dangerous method used cautiously in this class
private void AddOrUpdate(TKey1 key1, TKey2 key2)
{
_forwards[key1] = key2;
_reverses[key2] = key1;
}
public void AddOrUpdateKeyByKey2(TKey2 key2, TKey1 key1)
{
if (!UpdateByKey2(key2, key1))
AddOrUpdate(key1, key2);
}
public bool UpdateKey1(TKey1 oldKey, TKey1 newKey)
{
return UpdateKey(oldKey, _forwards, newKey, (key1, key2) => AddOrUpdate(key1, key2));
}
private static bool UpdateKey<S, T>(S oldKey, Dictionary<S, T> dict, S newKey, Action<S, T> updater)
{
T otherKey;
if (!TryGetValue(oldKey, dict, out otherKey) || ContainsKey(newKey, dict))
return false;
Remove(oldKey, dict);
updater(newKey, otherKey);
return true;
}
public bool UpdateKey2(TKey2 oldKey, TKey2 newKey)
{
return UpdateKey(oldKey, _reverses, newKey, (key1, key2) => AddOrUpdate(key2, key1));
}
public bool UpdateByKey1(TKey1 key1, TKey2 key2)
{
return UpdateByKey(key1, _forwards, _reverses, key2, (k1, k2) => AddOrUpdate(k1, k2));
}
private static bool UpdateByKey<S, T>(S key1, Dictionary<S, T> forwards, Dictionary<T, S> reverses, T key2,
Action<S, T> updater)
{
T otherKey;
if (!TryGetValue(key1, forwards, out otherKey) || ContainsKey(key2, reverses))
return false;
if (!Remove(otherKey, reverses))
throw new Exception("somewhere logic went wrong and your data got corrupt");
updater(key1, key2);
return true;
}
public bool UpdateByKey2(TKey2 key2, TKey1 key1)
{
return UpdateByKey(key2, _reverses, _forwards, key1, (k1, k2) => AddOrUpdate(k2, k1));
}
public bool RemoveByKey1(TKey1 key)
{
return RemoveByKey(key, _forwards, _reverses);
}
private static bool RemoveByKey<S, T>(S key, Dictionary<S, T> keyDict, Dictionary<T, S> valueDict)
{
T otherKey;
if (!TryGetValue(key, keyDict, out otherKey))
return false;
if (!Remove(key, keyDict) || !Remove(otherKey, valueDict))
throw new Exception("somewhere logic went wrong and your data got corrupt");
return true;
}
private static bool Remove<S, T>(S key, Dictionary<S, T> dict)
{
return dict.Remove(key);
}
public bool RemoveByKey2(TKey2 key)
{
return RemoveByKey(key, _reverses, _forwards);
}
public void Clear()
{
_forwards.Clear();
_reverses.Clear();
}
public IEnumerator<Tuple<TKey1, TKey2>> GetEnumerator()
{
if (_forwards.Count != _reverses.Count)
throw new Exception("somewhere logic went wrong and your data got corrupt");
foreach (var item in _forwards)
yield return Tuple.Create(item.Key, item.Value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Similar to my answer here
Few things to note:
I have implemented only IEnumerable<>. I don't think ICollection<> makes sense here since the method names all could be way different for this special collection structure. Up to you to decide what should go inside IEnumerable<>. So now you have collection initializer syntax too, like
var p = new BiDictionary<int, string> { 1, "a" }, { 2, "b" } };
I have attempted for some weird exceptions to be thrown here and there - just for data integrity. Just to be on the safer side so that you know if ever my code has bugs.
Performance: You can lookup for Value with either of the Keys, which means Get and Contains method require just 1 lookup (O(1)). Add requires 2 lookups and 2 adds. Update requires 1 lookup and 2 adds. Remove takes 3 lookups. All similar to accepted answer.
I have created such a class, using C5 collection classes.
public class Mapper<K,T> : IEnumerable<T>
{
C5.TreeDictionary<K,T> KToTMap = new TreeDictionary<K,T>();
C5.HashDictionary<T,K> TToKMap = new HashDictionary<T,K>();
/// <summary>
/// Initializes a new instance of the Mapper class.
/// </summary>
public Mapper()
{
KToTMap = new TreeDictionary<K,T>();
TToKMap = new HashDictionary<T,K>();
}
public void Add(K key, T value)
{
KToTMap.Add(key, value);
TToKMap.Add(value, key);
}
public bool ContainsKey(K key)
{
return KToTMap.Contains(key);
}
public int Count
{
get { return KToTMap.Count; }
}
public K this[T obj]
{
get
{
return TToKMap[obj];
}
}
public T this[K obj]
{
get
{
return KToTMap[obj];
}
}
public IEnumerator<T> GetEnumerator()
{
return KToTMap.Values.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return KToTMap.Values.GetEnumerator();
}
}
Another extension to the accepted answer. It implements IEnumerable so one can use foreach with that. I realize there are more answers with IEnumerable implementation but this one uses structs so it is garbage collector friendly.
This is especially usefull in Unity engine (checked with the profiler).
/// <summary>
/// This is a dictionary guaranteed to have only one of each value and key.
/// It may be searched either by TFirst or by TSecond, giving a unique answer because it is 1 to 1.
/// It implements garbage-collector-friendly IEnumerable.
/// </summary>
/// <typeparam name="TFirst">The type of the "key"</typeparam>
/// <typeparam name="TSecond">The type of the "value"</typeparam>
public class BiDictionary<TFirst, TSecond> : IEnumerable<BiDictionary<TFirst, TSecond>.Pair>
{
public struct Pair
{
public TFirst First;
public TSecond Second;
}
public struct Enumerator : IEnumerator<Pair>, IEnumerator
{
public Enumerator(Dictionary<TFirst, TSecond>.Enumerator dictEnumerator)
{
_dictEnumerator = dictEnumerator;
}
public Pair Current
{
get
{
Pair pair;
pair.First = _dictEnumerator.Current.Key;
pair.Second = _dictEnumerator.Current.Value;
return pair;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public void Dispose()
{
_dictEnumerator.Dispose();
}
public bool MoveNext()
{
return _dictEnumerator.MoveNext();
}
public void Reset()
{
throw new NotSupportedException();
}
private Dictionary<TFirst, TSecond>.Enumerator _dictEnumerator;
}
#region Exception throwing methods
/// <summary>
/// Tries to add the pair to the dictionary.
/// Throws an exception if either element is already in the dictionary
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
public void Add(TFirst first, TSecond second)
{
if (_firstToSecond.ContainsKey(first) || _secondToFirst.ContainsKey(second))
throw new ArgumentException("Duplicate first or second");
_firstToSecond.Add(first, second);
_secondToFirst.Add(second, first);
}
/// <summary>
/// Find the TSecond corresponding to the TFirst first
/// Throws an exception if first is not in the dictionary.
/// </summary>
/// <param name="first">the key to search for</param>
/// <returns>the value corresponding to first</returns>
public TSecond GetByFirst(TFirst first)
{
TSecond second;
if (!_firstToSecond.TryGetValue(first, out second))
throw new ArgumentException("first");
return second;
}
/// <summary>
/// Find the TFirst corresponing to the Second second.
/// Throws an exception if second is not in the dictionary.
/// </summary>
/// <param name="second">the key to search for</param>
/// <returns>the value corresponding to second</returns>
public TFirst GetBySecond(TSecond second)
{
TFirst first;
if (!_secondToFirst.TryGetValue(second, out first))
throw new ArgumentException("second");
return first;
}
/// <summary>
/// Remove the record containing first.
/// If first is not in the dictionary, throws an Exception.
/// </summary>
/// <param name="first">the key of the record to delete</param>
public void RemoveByFirst(TFirst first)
{
TSecond second;
if (!_firstToSecond.TryGetValue(first, out second))
throw new ArgumentException("first");
_firstToSecond.Remove(first);
_secondToFirst.Remove(second);
}
/// <summary>
/// Remove the record containing second.
/// If second is not in the dictionary, throws an Exception.
/// </summary>
/// <param name="second">the key of the record to delete</param>
public void RemoveBySecond(TSecond second)
{
TFirst first;
if (!_secondToFirst.TryGetValue(second, out first))
throw new ArgumentException("second");
_secondToFirst.Remove(second);
_firstToSecond.Remove(first);
}
#endregion
#region Try methods
/// <summary>
/// Tries to add the pair to the dictionary.
/// Returns false if either element is already in the dictionary
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns>true if successfully added, false if either element are already in the dictionary</returns>
public bool TryAdd(TFirst first, TSecond second)
{
if (_firstToSecond.ContainsKey(first) || _secondToFirst.ContainsKey(second))
return false;
_firstToSecond.Add(first, second);
_secondToFirst.Add(second, first);
return true;
}
/// <summary>
/// Find the TSecond corresponding to the TFirst first.
/// Returns false if first is not in the dictionary.
/// </summary>
/// <param name="first">the key to search for</param>
/// <param name="second">the corresponding value</param>
/// <returns>true if first is in the dictionary, false otherwise</returns>
public bool TryGetByFirst(TFirst first, out TSecond second)
{
return _firstToSecond.TryGetValue(first, out second);
}
/// <summary>
/// Find the TFirst corresponding to the TSecond second.
/// Returns false if second is not in the dictionary.
/// </summary>
/// <param name="second">the key to search for</param>
/// <param name="first">the corresponding value</param>
/// <returns>true if second is in the dictionary, false otherwise</returns>
public bool TryGetBySecond(TSecond second, out TFirst first)
{
return _secondToFirst.TryGetValue(second, out first);
}
/// <summary>
/// Remove the record containing first, if there is one.
/// </summary>
/// <param name="first"></param>
/// <returns> If first is not in the dictionary, returns false, otherwise true</returns>
public bool TryRemoveByFirst(TFirst first)
{
TSecond second;
if (!_firstToSecond.TryGetValue(first, out second))
return false;
_firstToSecond.Remove(first);
_secondToFirst.Remove(second);
return true;
}
/// <summary>
/// Remove the record containing second, if there is one.
/// </summary>
/// <param name="second"></param>
/// <returns> If second is not in the dictionary, returns false, otherwise true</returns>
public bool TryRemoveBySecond(TSecond second)
{
TFirst first;
if (!_secondToFirst.TryGetValue(second, out first))
return false;
_secondToFirst.Remove(second);
_firstToSecond.Remove(first);
return true;
}
#endregion
/// <summary>
/// The number of pairs stored in the dictionary
/// </summary>
public Int32 Count
{
get { return _firstToSecond.Count; }
}
/// <summary>
/// Removes all items from the dictionary.
/// </summary>
public void Clear()
{
_firstToSecond.Clear();
_secondToFirst.Clear();
}
public Enumerator GetEnumerator()
{
//enumerator.Reset(firstToSecond.GetEnumerator());
return new Enumerator(_firstToSecond.GetEnumerator());
}
IEnumerator<Pair> IEnumerable<Pair>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private Dictionary<TFirst, TSecond> _firstToSecond = new Dictionary<TFirst, TSecond>();
private Dictionary<TSecond, TFirst> _secondToFirst = new Dictionary<TSecond, TFirst>();
}
A bit late, but here's an implementation I wrote a while back. It handles a few interesting edge cases, such as when the key overrides the equality check to perform partial equality. This results in the main dictionary storing A => 1 but the inverse storing 1 => A'.
You access the inverse dictionary via the Inverse property.
var map = new BidirectionalDictionary<int, int>();
map.Add(1, 2);
var result = map.Inverse[2]; // result is 1
//
// BidirectionalDictionary.cs
//
// Author:
// Chris Chilvers <chilversc#googlemail.com>
//
// Copyright (c) 2009 Chris Chilvers
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
namespace Cadenza.Collections
{
public class BidirectionalDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly IEqualityComparer<TKey> keyComparer;
private readonly IEqualityComparer<TValue> valueComparer;
private readonly Dictionary<TKey, TValue> keysToValues;
private readonly Dictionary<TValue, TKey> valuesToKeys;
private readonly BidirectionalDictionary<TValue, TKey> inverse;
public BidirectionalDictionary () : this (10, null, null) {}
public BidirectionalDictionary (int capacity) : this (capacity, null, null) {}
public BidirectionalDictionary (IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer)
: this (10, keyComparer, valueComparer)
{
}
public BidirectionalDictionary (int capacity, IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer)
{
if (capacity < 0)
throw new ArgumentOutOfRangeException ("capacity", capacity, "capacity cannot be less than 0");
this.keyComparer = keyComparer ?? EqualityComparer<TKey>.Default;
this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
keysToValues = new Dictionary<TKey, TValue> (capacity, this.keyComparer);
valuesToKeys = new Dictionary<TValue, TKey> (capacity, this.valueComparer);
inverse = new BidirectionalDictionary<TValue, TKey> (this);
}
private BidirectionalDictionary (BidirectionalDictionary<TValue, TKey> inverse)
{
this.inverse = inverse;
keyComparer = inverse.valueComparer;
valueComparer = inverse.keyComparer;
valuesToKeys = inverse.keysToValues;
keysToValues = inverse.valuesToKeys;
}
public BidirectionalDictionary<TValue, TKey> Inverse {
get { return inverse; }
}
public ICollection<TKey> Keys {
get { return keysToValues.Keys; }
}
public ICollection<TValue> Values {
get { return keysToValues.Values; }
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator ()
{
return keysToValues.GetEnumerator ();
}
IEnumerator IEnumerable.GetEnumerator ()
{
return GetEnumerator ();
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo (KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<TKey, TValue>>) keysToValues).CopyTo (array, arrayIndex);
}
public bool ContainsKey (TKey key)
{
if (key == null)
throw new ArgumentNullException ("key");
return keysToValues.ContainsKey (key);
}
public bool ContainsValue (TValue value)
{
if (value == null)
throw new ArgumentNullException ("value");
return valuesToKeys.ContainsKey (value);
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains (KeyValuePair<TKey, TValue> item)
{
return ((ICollection<KeyValuePair<TKey, TValue>>) keysToValues).Contains (item);
}
public bool TryGetKey (TValue value, out TKey key)
{
if (value == null)
throw new ArgumentNullException ("value");
return valuesToKeys.TryGetValue (value, out key);
}
public bool TryGetValue (TKey key, out TValue value)
{
if (key == null)
throw new ArgumentNullException ("key");
return keysToValues.TryGetValue (key, out value);
}
public TValue this[TKey key] {
get { return keysToValues [key]; }
set {
if (key == null)
throw new ArgumentNullException ("key");
if (value == null)
throw new ArgumentNullException ("value");
//foo[5] = "bar"; foo[6] = "bar"; should not be valid
//as it would have to remove foo[5], which is unexpected.
if (ValueBelongsToOtherKey (key, value))
throw new ArgumentException ("Value already exists", "value");
TValue oldValue;
if (keysToValues.TryGetValue (key, out oldValue)) {
// Use the current key for this value to stay consistent
// with Dictionary<TKey, TValue> which does not alter
// the key if it exists.
TKey oldKey = valuesToKeys [oldValue];
keysToValues [oldKey] = value;
valuesToKeys.Remove (oldValue);
valuesToKeys [value] = oldKey;
} else {
keysToValues [key] = value;
valuesToKeys [value] = key;
}
}
}
public int Count {
get { return keysToValues.Count; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
get { return false; }
}
public void Add (TKey key, TValue value)
{
if (key == null)
throw new ArgumentNullException ("key");
if (value == null)
throw new ArgumentNullException ("value");
if (keysToValues.ContainsKey (key))
throw new ArgumentException ("Key already exists", "key");
if (valuesToKeys.ContainsKey (value))
throw new ArgumentException ("Value already exists", "value");
keysToValues.Add (key, value);
valuesToKeys.Add (value, key);
}
public void Replace (TKey key, TValue value)
{
if (key == null)
throw new ArgumentNullException ("key");
if (value == null)
throw new ArgumentNullException ("value");
// replaces a key value pair, if the key or value already exists those mappings will be replaced.
// e.g. you have; a -> b, b -> a; c -> d, d -> c
// you add the mapping; a -> d, d -> a
// this will remove both of the original mappings
Remove (key);
inverse.Remove (value);
Add (key, value);
}
void ICollection<KeyValuePair<TKey, TValue>>.Add (KeyValuePair<TKey, TValue> item)
{
Add (item.Key, item.Value);
}
public bool Remove (TKey key)
{
if (key == null)
throw new ArgumentNullException ("key");
TValue value;
if (keysToValues.TryGetValue (key, out value)) {
keysToValues.Remove (key);
valuesToKeys.Remove (value);
return true;
}
else {
return false;
}
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove (KeyValuePair<TKey, TValue> item)
{
bool removed = ((ICollection<KeyValuePair<TKey, TValue>>) keysToValues).Remove (item);
if (removed)
valuesToKeys.Remove (item.Value);
return removed;
}
public void Clear ()
{
keysToValues.Clear ();
valuesToKeys.Clear ();
}
private bool ValueBelongsToOtherKey (TKey key, TValue value)
{
TKey otherKey;
if (valuesToKeys.TryGetValue (value, out otherKey))
// if the keys are not equal the value belongs to another key
return !keyComparer.Equals (key, otherKey);
else
// value doesn't exist in map, thus it cannot belong to another key
return false;
}
}
}
Original source and tests on github.
How can I change the way a dictionary works, so that if there is no KVP with given key, it returns a default value, without wrapping usual dic["nonexistentKey"] with try-catch?
You could make your own class which encapsulates a Dictionary<TKey,TValue>, and implements IDictionary<TKey,TValue>.
This will behave like a dictionary, but you can write the behavior to handle your non-existent key any way you wish.
However, you can't change the way the actual Dictionary<TKey,TValue> class functions.
You can also add an extension method to IDictionary, or Dictionary if you prefer.
public static class IDictionaryExtensions
{
public static TValue ValueAtOrDefault<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
{
if (dictionary == null || !dictionary.ContainsKey(key))
{
return defaultValue;
}
return dictionary[key];
}
}
Note that you may want to throw an ArgumentNullException if the dictionary is null, rather than returning the default value as in the example... whatever is appropriate for you.
Use Dictionary.TryGetValue :
Dictionary<int, YourType> dictionary = ...;
YourType x;
if (!dictionary.TryGetValue(123, out x))
{
x = new YourType();
}
// here X will be assigned to the value or the default if the key was not present.
If you really need to override the default dictionary[key] approach, you can use this class (as either the dictionary itself or as a wrapper for an existing dictionary):
/// <summary>
/// A dictionary implementation that returns the default value of <typeparamref name="TValue"/> when the key is not present in the dictionary.
/// </summary>
public class DictionaryWithDefaults<TKey, TValue> : IDictionary<TKey, TValue>
{
/// <summary>
/// Holds the actual data using standard dictionary.
/// </summary>
private IDictionary<TKey, TValue> _storage;
/// <summary>
/// Initializes a new instance of the <see cref="DictionaryWithDefaults{TValue}" /> class.
/// The data is stored directly in this dictionary.
/// </summary>
public DictionaryWithDefaults()
{
this._storage = new Dictionary<TKey, TValue>();
}
/// <summary>
/// Initializes a new instance of the <see cref="DictionaryWithDefaults{TValue}" /> class.
/// This dictionary acts as a wrapper for the data stored in the dictionary <paramref name="forWrapping" />.
/// </summary>
/// <param name="forWrapping">The dictionary object for wrapping.</param>
/// <exception cref="System.ArgumentNullException">when <paramref name="forWrapping"/> is <c>null</c></exception>
public DictionaryWithDefaults(IDictionary<TKey, TValue> forWrapping)
{
if (forWrapping == null)
throw new ArgumentNullException("forWrapping");
this._storage = forWrapping;
}
public void Add(TKey key, TValue value)
{
this._storage.Add(key, value);
}
public bool ContainsKey(TKey key)
{
return this._storage.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return this._storage.Keys; }
}
public bool Remove(TKey key)
{
return this._storage.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
// always return a value, even if the key does not exist.
// this is also the only place one would modify if the default value has to be customized (passed in the constructor etc.)
if (!this._storage.TryGetValue(key, out value))
value = default(TValue);
return true;
}
public ICollection<TValue> Values
{
get { return this._storage.Values; }
}
public TValue this[TKey key]
{
get
{
TValue value;
this.TryGetValue(key, out value);
return value;
}
set
{
this._storage[key] = value;
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this._storage.Add(item);
}
public void Clear()
{
this._storage.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return this._storage.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
this._storage.CopyTo(array, arrayIndex);
}
public int Count
{
get { return this._storage.Count; }
}
public bool IsReadOnly
{
get { return this._storage.IsReadOnly; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return this._storage.Remove(item);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return this._storage.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this._storage.GetEnumerator();
}
}
No, you have to wrap it. this[] is not a virtual method and cannot be overridden.
So your best bet is to create a simple class that exposes IDictionary (or only selected methods) and that wraps the this[] get with a try/catch.
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 |
+----------+----------------+----------------+--------------------------------+