Skip to content

Instantly share code, notes, and snippets.

@jjxtra
Last active March 10, 2021 17:34
Show Gist options
  • Save jjxtra/a936a01db4cb5c62a11401c8c80c782c to your computer and use it in GitHub Desktop.
Save jjxtra/a936a01db4cb5c62a11401c8c80c782c to your computer and use it in GitHub Desktop.
The .NET Dictionary class will mostly preserve the insertion order of items when enumerated, until you start removing stuff - then there are no guarantees. Regardless, the documentation says order is undefined. This custom dictionary class preserves insertion order during all enumeration calls.
#nullable disable
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace System.Collections.Generic
{
/// <summary>
/// Dictionary that preserves inserted order of items during iteration.
/// </summary>
/// <typeparam name="TKey">TKey</typeparam>
/// <typeparam name="TValue">TValue</typeparam>
public class OrderPreservingDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TKey : notnull
{
private readonly List<TKey> keys = new(); // faster GC free iteration of keys
private readonly List<TValue> values = new(); // faster GC free iteration of values
private readonly Dictionary<TKey, TValue> dict; // for fast lookup
private readonly IEqualityComparer<TKey> keyComparer;
private bool TryGetValueInternal(TKey key, [MaybeNullWhen(false)] out TValue value)
{
return dict.TryGetValue(key, out value);
}
private void SetValue(TKey key, TValue value)
{
dict[key] = value;
for (int i = 0; i < keys.Count; i++)
{
if (keyComparer.Equals(key, keys[i]))
{
keys[i] = key;
values[i] = value;
return;
}
}
keys.Add(key);
values.Add(value);
}
private bool RemoveValue(TKey key)
{
if (dict.Remove(key))
{
for (int i = 0; i < keys.Count; i++)
{
if (keyComparer.Equals(key, keys[i]))
{
keys.RemoveAt(i);
values.RemoveAt(i);
return true;
}
}
}
return false;
}
/// <summary>
/// Constructor
/// </summary>
public OrderPreservingDictionary()
{
keyComparer = EqualityComparer<TKey>.Default;
dict = new();
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="keyComparer">Key comparer</param>
public OrderPreservingDictionary(IEqualityComparer<TKey> keyComparer)
{
this.keyComparer = keyComparer;
dict = new(this.keyComparer);
}
/// <inheritdoc />
public TValue this[TKey key]
{
get
{
TryGetValueInternal(key, out TValue value);
return value;
}
set => SetValue(key, value);
}
/// <inheritdoc />
public ICollection<TKey> Keys => keys;
/// <inheritdoc />
public ICollection<TValue> Values => values;
/// <inheritdoc />
public int Count => keys.Count;
/// <inheritdoc />
public bool IsReadOnly => false;
/// <inheritdoc />
public void Add(TKey key, TValue value)
{
SetValue(key, value);
}
/// <inheritdoc />
public void Add(KeyValuePair<TKey, TValue> item)
{
SetValue(item.Key, item.Value);
}
/// <inheritdoc />
public void Clear()
{
dict.Clear();
keys.Clear();
values.Clear();
}
/// <inheritdoc />
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return dict.ContainsKey(item.Key);
}
/// <inheritdoc />
public bool ContainsKey(TKey key)
{
return dict.ContainsKey(key);
}
/// <inheritdoc />
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
for (int i = arrayIndex; i < array.Length && i < keys.Count; i++)
{
array[i] = new KeyValuePair<TKey, TValue>(keys[i], values[i]);
}
}
/// <inheritdoc />
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
for (int i = 0; i < keys.Count; i++)
{
yield return new KeyValuePair<TKey, TValue>(keys[i], values[i]);
}
}
/// <inheritdoc />
public bool Remove(TKey key)
{
return RemoveValue(key);
}
/// <inheritdoc />
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return RemoveValue(item.Key);
}
/// <inheritdoc />
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
return TryGetValueInternal(key, out value);
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
for (int i = 0; i < keys.Count; i++)
{
yield return new KeyValuePair<TKey, TValue>(keys[i], values[i]);
}
}
}
}
#nullable restore
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment