Nessuna implementazione generica di OrderedDictionary?


136

Non sembra esserci un'implementazione generica di OrderedDictionary(che si trova nello System.Collections.Specializedspazio dei nomi) in .NET 3.5. Ce n'è uno che mi manca?

Ho trovato implementazioni là fuori per fornire la funzionalità, ma mi chiedevo se / perché non c'è un'implementazione generica pronta all'uso e se qualcuno sa se si tratta di qualcosa in .NET 4.0?


1
Ecco un'implementazione di un OrderedDictionary<T>: codeproject.com/Articles/18615/…
Tim Schmelter,


La mia implementazione di OrderedDictionary <T> ha O (1) insert / delete perché utilizza un LinkedList invece di ArrayList per mantenere l'ordine di inserimento: clintonbrennan.com/2013/12/…
Clinton

2
Se devi solo essere in grado di scorrere le voci nell'ordine in cui sono state aggiunte, allora List <KeyValuePair <TKey, TValue >> potrebbe essere abbastanza buono. (Certo, non è una soluzione generale, ma abbastanza buono per alcuni scopi.)
yoyo

1
È una sfortunata omissione. Esistono altri tipi di dati validi Systems.Collections.Generic. Richiediamo OrderedDictionary<TKey,TValue>.NET 5. Come altri hanno sottolineato, il caso in cui la chiave è un int è degenerato e avrà bisogno di cure particolari.
Colonnello Panic,

Risposte:



95

L'implementazione di un generico OrderedDictionarynon è tremendamente difficile, ma richiede inutilmente tempo e francamente questa classe è una grande svista da parte di Microsoft. Esistono diversi modi per implementarlo, ma ho scelto di utilizzare a KeyedCollectionper la mia memoria interna. Ho anche scelto di implementare vari metodi per l'ordinamento del modo in cui List<T>questo è essenzialmente un IList ibrido e IDictionary. Ho incluso la mia implementazione qui per i posteri.

Ecco l'interfaccia. Si noti che include System.Collections.Specialized.IOrderedDictionary, che è la versione non generica di questa interfaccia fornita da 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);
    }

}

Ecco l'implementazione insieme alle classi di supporto:

// 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; } }
    }
}

E nessuna implementazione sarebbe completa senza alcuni test (ma tragicamente, SO non mi permetterà di pubblicare quel codice in un solo post), quindi dovrò lasciarti per scrivere i tuoi test. Ma ne ho lasciati alcuni in modo da poter avere un'idea di come funziona:

// 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);
            }
        }

-- AGGIORNARE --

Fonte per questa e altre utili librerie .NET mancanti davvero utili qui: https://github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrderedDictionary.cs


6
Di pubblico dominio, stai chiedendo se puoi usarlo, modificarlo e trattarlo come se fosse tuo senza preoccupazioni - sì. Sentiti libero. Se intendi la licenza e ospitarla da qualche parte - no ... vive qui su SO solo per ora.
mattmc3

3
@ mattmc3 Grazie per il tuo codice signore, ma il tuo commento su questioni di dominio pubblico mi riguarda, quando hai detto nel commento: "Se intendi la licenza e ospitala da qualche parte - no ... vive qui su SO solo per ora. " Con tutto il rispetto (veramente inteso) per l'autore, hai davvero il diritto di fare quella restrizione, dopo aver pubblicato il codice su SO ??? Ad esempio, qualcuno di noi ha davvero il diritto di impedire che il nostro codice su SO venga pubblicato, ad esempio, come idea? Chiunque?
Nicholas Petersen,

6
@ NicholasPetersen - Penso che tu abbia frainteso. In risposta diretta al colonnello Panic, l'ho informato che personalmente non l'ho autorizzato né ospitato altrove. (In realtà, da quando l'hai menzionato, ho fatto un riassunto : gist.github.com/mattmc3/6486878 ). Ma questo è un codice senza licenza. Prendilo e fai quello che vuoi con esso. L'ho scritto al 100% per uso personale. È libero. Godere. In effetti, se qualcuno di Microsoft legge mai questo, mi aspetto che facciano il loro dovere e alla fine lo inseriscano nella prossima versione di .NET. Nessuna attribuzione necessaria.
mattmc3,

2
E se TKeyfosse int? Come this[]funzionerà in questo caso?
VB,

2
@klicker - Usa solo la normale indicizzazione in stile array. L'ordine di inserimento viene gestito proprio come un elenco. La conversione del tipo gestisce la determinazione se intendevi indicizzare con un int o fare riferimento tramite il tipo di chiave. Se il tipo di chiave è un int, è necessario utilizzare il metodo GetValue ().
mattmc3,

32

Per la cronaca, esiste un KeyedCollection generico che consente agli oggetti di essere indicizzati da un int e una chiave. La chiave deve essere incorporata nel valore.


2
Questo non mantiene l'ordine di inizializzazione come OrderedDictionary! Dai un'occhiata alla mia risposta.
JoelFan,

14
Mantiene l'ordine di aggiunta / inserimento.
Guillaume,

sì, sì ... dove avete avuto l'idea che il keyedcollection ordina gli oggetti ... sono incappato in questa seconda volta
Boppity Bop,

1
Sicuramente mantiene l'ordine di inizializzazione. Link utili includono stackoverflow.com/a/11802824/9344 e geekswithblogs.net/NewThingsILearned/archive/2010/01/07/... .
Ted

+1, questa sembra la migliore soluzione nel framework. Dover implementare la classe astratta per ogni coppia di tipi che vuoi usare è una specie di trascinamento. Potresti farlo con un'implementazione generica che richiede un'interfaccia, ma poi dovresti implementare l'interfaccia su ogni tipo che desideri poter archiviare.
DCShannon,

19

Ecco una scoperta bizzarra: lo spazio dei nomi System.Web.Util in System.Web.Extensions.dll contiene un OrderedDictionary generico

// 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

Non so perché MS l'abbia inserito lì invece del pacchetto System.Collections.Generic, ma suppongo che puoi semplicemente copiare e incollare il codice e utilizzarlo (è interno, quindi non puoi usarlo direttamente). Sembra che l'implementazione utilizzi un dizionario standard e elenchi chiave / valore separati. Abbastanza diretto...


2
System.Runtime.Collectionscontiene anche un interno OrderedDictionary<TKey, TValue>che avvolge solo la versione non generica
VB

1
System.Web.Util.OrderedDictionary<TKey, TValue>è implementato internamente attorno a generico Dictionary. Stranamente non si attua IListmaICollection<KeyValuePair<TKey, TValue>>
Mikhail,

1
@rboy Come ho detto, era interno e ha concluso l'implementazione non generica. Ma è stato 3+ anni fa ... Per dimensioni inferiori a un paio di centinaia la ricerca lineare List<KeyValuePair<TKey,TValue>>sarà competitiva a causa del modello di accesso alla memoria, per dimensioni più grandi basta usare lo stesso elenco + Dictionary<TKey,int>come una ricerca. AFAIK non esiste una tale struttura di dati che funzioni meglio in termini di velocità / memoria in BigO.
VB

1
@rboy qui è il link a quello generico , fa riferimento a quello non generico che usa HashTable. Scommetto davvero che per dimensioni ridotte sarà più veloce usare la ricerca lineare su List / Array generici.
VB,

1
@PeterMortensen System.Collections.Specialized.OrderedDictionarynon è un tipo generico. Guarda, nessuna parentesi angolare nella pagina del documento che hai collegato: P
user7610

17

Per quello che vale, ecco come l'ho risolto:

   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;
        }
    }

Può essere inizializzato in questo modo:

var pairList = new PairList<string, string>
    {
        { "pitcher", "Ken" },
        { "catcher", "Brad"},
        { "left fielder", "Stan"},
    };

e vi si accedeva in questo modo:

foreach (var pair in pairList)
{
    Console.WriteLine("position: {0}, player: {1}",
        pair.Key, pair.Value);
}

// Guaranteed to print in the order of initialization

3
Grazie! Non mi ero reso conto che gli inizializzatori di raccolta fossero solo una sintassi speciale per i Addmetodi.
Sam,

10
Questo non è un dizionario. Dizionario significa indicizzazione con chiave e nessuna chiave duplicata .
nawfal,

ma se ti capita di non aver bisogno dell'indicizzazione per chiave (che non è troppo difficile da aggiungere) e per le chiavi dupliacte, questo è utile
stijn

1
Hai un problema con le chiamate in codice pairList.Add(new KeyValuePair<K,V>())(ovvero il metodo sulla Listclasse). In tal caso, il itsIndexdizionario non viene aggiornato e ora l'elenco e il dizionario non sono sincronizzati. Potrebbe nascondere il List.Addmetodo creando un new PairList.Add(KeyValuePair<K,V>)metodo o utilizzare la composizione anziché l'ereditarietà e implementare Listnuovamente tutti i metodi ... molto più codice ...
Neizan,

1
@nawfal, per rispondere alle tue preoccupazioni, potresti semplicemente aggiungere un segno di spunta come: if (itsindex.Contains(key)) return;all'inizio di Addprevenire i duplicati
JoelFan

14

Un grave problema concettuale con una versione generica di OrderedDictionaryè che gli utenti di OrderedDictionary<TKey,TValue>si aspetterebbero di essere in grado di indicizzarlo numericamente usando un int, o cercando usando un TKey. Quando l'unico tipo di chiave era Object, come nel caso di un non generico OrderedDictionary, il tipo di argomento passato all'indicizzatore sarebbe sufficiente per distinguere se quale tipo di operazione di indicizzazione debba essere eseguita. Tuttavia, non è chiaro come OrderedDictionary<int, TValue>dovrebbe comportarsi l'indicizzatore di un .

Se classi come Drawing.Pointavevano raccomandato e seguito una regola secondo cui le strutture mutabili a tratti dovrebbero esporre i loro elementi mutabili come campi anziché come proprietà e astenersi dall'utilizzare setter di proprietà che modificano this, allora si OrderedDictionary<TKey,TValue>potrebbe esporre in modo efficiente una ByIndexproprietà che restituiva una Indexerstruttura che conteneva un riferimento a il dizionario e aveva una proprietà indicizzata di cui getter e setter avrebbero richiamato GetByIndexe SetByIndexsu di esso. Pertanto, si potrebbe dire qualcosa come MyDict.ByIndex[5] += 3;aggiungere 3 al sesto elemento del dizionario.

Sfortunatamente, affinché il compilatore accetti una cosa del genere, sarebbe necessario fare in modo che la ByIndexproprietà restituisca una nuova istanza di classe anziché una struttura ogni volta che viene invocata, eliminando i vantaggi che si otterrebbero evitando la boxe.

In VB.NET, si potrebbe aggirare il problema utilizzando una proprietà indicizzata denominata (quindi MyDict.ByIndex[int]sarebbe un membro di MyDict, piuttosto che richiedere MyDict.ByIndexdi essere un membro di MyDictcui include un indicizzatore), ma C # non consente tali cose.

Potrebbe essere valsa la pena offrire un OrderedDictionary<TKey,TValue> where TKey:class, ma gran parte della ragione per fornire generici in primo luogo era consentire il loro uso con tipi di valore.


È utile sottolineare che intle chiavi di tipo rappresentano una sfida, ma potrebbero essere evitate seguendo l'esempio del SortedList<TKey, TValue>tipo correlato : supportano solo le chiavi [...]e ne richiedono l'uso .Values[...]per l'accesso tramite indice. ( SortedDictionary<TKey, TValue>al contrario, che non è ottimizzato per l'accesso indicizzato, richiede l'uso di .ElementAt(...).Value)
mklement0

7

Per molti scopi ho scoperto che uno può cavarsela con a List<KeyValuePair<K, V>>. (Non se ne hai bisogno per estenderlo Dictionary, ovviamente, e non se hai bisogno di una ricerca del valore-chiave migliore di O (n).)


Sono appena arrivato alla stessa conclusione!
Peter,

1
Come ho detto, "per molti scopi".
David Moles,

2
Puoi anche utilizzare a Tuple<T1, T2>se non hanno una relazione valore-chiave.
cdmckay,

1
Qual è il punto di avere coppie valore-chiave se non puoi indicizzare per chiave?
DCShannon,

1
@DCShannon Puoi ancora mappare le chiavi sui valori, non puoi cercarle più velocemente di O (n) (o gestire automaticamente le chiavi duplicate). Per i piccoli valori di n questo a volte è abbastanza buono, specialmente in situazioni in cui stai comunque iterando comunemente tutte le chiavi.
David Moles,

5

C'è SortedDictionary<TKey, TValue>. Anche se semanticamente vicino, non sto affermando che è lo stesso OrderedDictionarysemplicemente perché non lo sono. Anche dalle caratteristiche prestazionali. Tuttavia la differenza molto interessante e abbastanza importante tra Dictionary<TKey, TValue>(e in quella misura OrderedDictionarye implementazioni fornite nelle risposte) ed SortedDictionaryè che quest'ultimo sta usando un albero binario sotto. Questa è una distinzione fondamentale perché rende la classe immune ai vincoli di memoria applicati alla classe generica. Vedi questa discussione su OutOfMemoryExceptionsgenerata quando viene utilizzata la classe generica per gestire un ampio set di coppie chiave-valore.

Come capire il valore massimo per il parametro di capacità passato al costruttore del dizionario per evitare OutOfMemoryException?


C'è un modo per ottenere le chiavi o i valori di SortedDictionary a nell'ordine in cui sono stati aggiunti ? Questo è ciò che OrderedDictionaryconsente. Concetto diverso da quello ordinato . (Ho commesso questo errore in passato; pensavo che fornire un comparatore al costruttore OrderedDictionary lo avrebbe reso ordinato, ma tutto ciò che fa con il comparatore è determinare l'uguaglianza delle chiavi; ad esempio un comparatore insensibile alle stringhe consente la ricerca delle chiavi insensibili alle stringhe.)
ToolmakerSteve

5

Bene, è una sfortunata omissione. Mi manca Python's OrderedDict

Un dizionario che ricorda l'ordine in cui le chiavi sono state inserite per la prima volta. Se una nuova voce sovrascrive una voce esistente, la posizione di inserimento originale rimane invariata. L'eliminazione di una voce e il suo reinserimento lo sposta alla fine.

Quindi ho scritto la mia OrderedDictionary<K,V>lezione in C #. Come funziona? Mantiene due raccolte: un dizionario non ordinato alla vaniglia e un elenco ordinato di chiavi. Con questa soluzione, le operazioni del dizionario standard mantengono le loro complessità veloci e anche la ricerca per indice è veloce.

https://gist.github.com/hickford/5137384

Ecco l'interfaccia

/// <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);
}

3

Come seguito al commento di @VB ecco un'implementazione accessibile di System.Runtime.Collections.OrderedDictionary <,> . Inizialmente stavo per accedervi per riflessione e fornirlo tramite una fabbrica, ma la DLL in cui si trova non sembra essere molto accessibile, quindi ho appena estratto la fonte stessa.

Una cosa da notare è che l'indicizzatore qui non genererà KeyNotFoundException . Odio assolutamente quella convenzione e quella è stata la libertà che ho preso in questa implementazione. Se questo è importante per te, basta sostituire la linea per return default(TValue);. Utilizza C # 6 ( compatibile con 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;
    }
}

Estrai richieste / discussioni accettate su GitHub


3

Per coloro che cercano un'opzione di pacchetto "ufficiale" in NuGet, un'implementazione di un OrderedDictionary generico è stata accettata in .NET CoreFX Lab. Se tutto va bene, il tipo verrà infine approvato e integrato nel repository .NET CoreFX principale.

È possibile che questa implementazione venga respinta.

È possibile fare riferimento all'implementazione impegnata qui https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/OrderedDictionary.cs

Il pacchetto NuGet che ha definitivamente questo tipo disponibile per l'uso può essere trovato qui https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3

Oppure puoi installare il pacchetto in Visual Studio. Cerca il pacchetto "Microsoft.Experimental.Collections" e assicurati che la casella di controllo "Includi prerelease" sia selezionata.

Aggiornerà questo post se e quando il tipo sarà reso ufficialmente disponibile.


Puoi stimare quando verrà rilasciato?
mvorisek,

Non sto prendendo parte allo sviluppo di questa biblioteca, quindi sfortunatamente non ne ho idea. È probabile che sarà una raccolta di framework integrata se mai viene "approvata".
Charlie

1

Ho implementato un generico OrderedDictionary<TKey, TValue>avvolgendolo SortedList<TKey, TValue>e aggiungendo un privato Dictionary<TKey, int> _order. Quindi ho creato un'implementazione interna di Comparer<TKey>, passando un riferimento al dizionario _order. Quindi uso questo comparatore per la SortedList interna. Questa classe mantiene l'ordine degli elementi passati al costruttore e l'ordine delle aggiunte.

Questa implementazione ha quasi le stesse grandi caratteristiche O di SortedList<TKey, TValue>quando l'aggiunta e la rimozione di _order è O (1). Ogni elemento occuperà (secondo il libro "C # 4 in breve", p. 292, tabella 7-1) spazio di memoria aggiuntivo di 22 (sovraccarico) + 4 (int) + dimensione TKey (supponiamo 8) = 34 Insieme al SortedList<TKey, TValue>sovraccarico di due byte, il sovraccarico totale è di 36 byte, mentre lo stesso libro afferma che non genericoOrderedDictionary ha un sovraccarico di 59 byte.

Se passo sorted=trueal costruttore, allora _order non viene usato affatto, OrderedDictionary<TKey, TValue>è esattamenteSortedList<TKey, TValue> con un lieve sovraccarico per il wrapping, se non del tutto significativo.

Ho intenzione di memorizzare non così tanti oggetti di riferimento di grandi dimensioni in OrderedDictionary<TKey, TValue> , quindi per me questo ca. Un sovraccarico di 36 byte è tollerabile.

Il codice principale è sotto. Il codice aggiornato completo è disponibile in questa sintesi .

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
    // ...
}

Ci sono almeno quattro diversi casi d'uso che posso vedere per qualcosa del genere OrderedDictionary, per quanto riguarda inserimenti o eliminazioni: (1) Non ci saranno mai cancellazioni; (2) ci saranno cancellazioni, ma ciò che è importante è che gli articoli siano elencati nell'ordine aggiunto; non è necessario l'accesso per indice; (3) l'indice numerico di un articolo dovrebbe (o almeno potrebbe) rimanere costante, e non più di 2 miliardi di articoli verranno aggiunti durante il ciclo di vita della raccolta, quindi se l'articolo 7 viene rimosso, non ci sarà mai più articolo # 7; (4) l'indice di un oggetto dovrebbe essere il suo rango rispetto ai sopravvissuti.
supercat,

1
Gli scenari n. 1 possono essere gestiti utilizzando un array in parallelo con Dictionary. Gli scenari n. 2 e n. 3 possono essere gestiti facendo in modo che ciascun elemento mantenga un indice che indichi quando è stato aggiunto e collegamenti a elementi aggiunti prima o dopo di esso. Lo scenario n. 4 è l'unico che sembrerebbe che non dovrebbe essere in grado di ottenere prestazioni O (1) per operazioni in sequenza arbitraria. A seconda dei modelli di utilizzo, il n. 4 può essere aiutato utilizzando varie strategie di aggiornamento pigro (mantenere i conteggi in un albero e rendere invalide le modifiche a un nodo anziché aggiornare il nodo e i relativi genitori).
supercat,

1
SortedList interno ha elementi nell'ordine di inserimento a causa dell'uso del comparatore personalizzato. Potrebbe essere lento ma il tuo commento sull'enumerazione è sbagliato. Mostra i test sull'enumerazione ...
VB

1
Di quale linea stai parlando con ToDictionary? Non influisce sull'elenco interno, ma solo sul dizionario degli ordini.
VB

1
@VB Mi scuso, ho perso entrambi. Come tale non l'ho testato. Quindi l'unico problema sarebbe con l'aggiunta. Due ricerche nel dizionario e due inserimenti. Annullerò il downvote.
nawfal,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.