Last active
March 10, 2021 17:34
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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