【问题标题】:Generic Key/Value pair collection in that preserves insertion order?保留插入顺序的通用键/值对集合?
【发布时间】:2009-09-08 22:42:41
【问题描述】:

我正在寻找类似 Dictionary 的东西,但要保证它保留插入顺序。由于 Dictionary 是一个哈希表,我认为它不是。

是否有一个通用集合,或者我需要使用旧的 .NET 1.1 集合之一?

【问题讨论】:

    标签: c# .net data-structures


    【解决方案1】:

    没有。但是,System.Collections.Specialized.OrderedDictionary 应该可以解决大部分需求。

    编辑:另一个选择是把它变成一个通用的。我尚未对其进行测试,但它可以编译(C# 6)并且应该可以工作。但是,它仍然具有 Ondrej Petrzilka 在下面的 cmets 中提到的相同限制。

        public class OrderdDictionary<T, K>
        {
            public OrderedDictionary UnderlyingCollection { get; } = new OrderedDictionary();
    
            public K this[T key]
            {
                get
                {
                    return (K)UnderlyingCollection[key];
                }
                set
                {
                    UnderlyingCollection[key] = value;
                }
            }
    
            public K this[int index]
            {
                get
                {
                    return (K)UnderlyingCollection[index];
                }
                set
                {
                    UnderlyingCollection[index] = value;
                }
            }
            public ICollection<T> Keys => UnderlyingCollection.Keys.OfType<T>().ToList();
            public ICollection<K> Values => UnderlyingCollection.Values.OfType<K>().ToList();
            public bool IsReadOnly => UnderlyingCollection.IsReadOnly;
            public int Count => UnderlyingCollection.Count;
            public IDictionaryEnumerator GetEnumerator() => UnderlyingCollection.GetEnumerator();
            public void Insert(int index, T key, K value) => UnderlyingCollection.Insert(index, key, value);
            public void RemoveAt(int index) => UnderlyingCollection.RemoveAt(index);
            public bool Contains(T key) => UnderlyingCollection.Contains(key);
            public void Add(T key, K value) => UnderlyingCollection.Add(key, value);
            public void Clear() => UnderlyingCollection.Clear();
            public void Remove(T key) => UnderlyingCollection.Remove(key);
            public void CopyTo(Array array, int index) => UnderlyingCollection.CopyTo(array, index);
        }
    

    【讨论】:

    • 不是通用的,但看起来足够接近。谢谢。
    • OrderedDictionary 很好,除非您需要 O(1) 删除。它通过键进行 O(n) 删除(它搜索索引,然后移动数组中的项目)。只有快速删除是从末尾和索引。
    • 这不应被标记为答案,因为它不支持 OP 所要求的泛型。 “足够接近”仍然不能让其他人看到这个答案。
    • @JohnStock 怎么样?
    • 很遗憾这使用了 OrderedDictionary 并招致了装箱。
    【解决方案2】:

    实际上有一个,它是通用的,自 .net 2.0 以来一直存在。它被称为KeyedCollection&lt;TKey, TItem&gt;但是,它具有从值构造键的限制,因此它不是通用的键/值对集合。 (尽管您当然可以像 KeyedCollection&lt;TKey, Tuple&lt;TKey, TItem&gt;&gt; 一样使用它作为解决方法)。

    如果您需要它作为IDictionary&lt;TKey, TItem&gt;,它有一个.Dictionary 属性。

    我遇到的一个小问题是它是一个抽象类,您必须对其进行子类化并实现:

    protected abstract TKey GetKeyForItem(TItem item)
    

    为此,我宁愿只将 lambda 传递给构造函数,但话又说回来,我猜虚拟方法比 lambda 稍快(对此表示赞赏)。

    编辑 正如 cmets 中提出的问题:KeyedCollection 保留顺序,因为它继承自 Collection&lt;T&gt;,它确实(它派生自 IList&lt;T&gt;。另请参阅添加方法:将一个对象添加到集合的末尾。)。

    【讨论】:

    • 您确定 KeyedCollection 保留顺序吗?
    • 它保留了顺序,因为它继承自 Collection<T>,确实如此。另请参阅 Add 方法的文档:将对象添加到 Collection 的末尾。 (继承自 Collection。).
    • 如果有GetKeyForItem,这是否意味着列表不能包含重复值?
    • @ToolmakerSteve 你错了有两个原因。首先,规范保证了插入顺序(只需阅读我对这个答案的第一条评论)。因此,如果它在内部确实是“简单的字典”,那将是一个严重的错误。其次,再次检查the current source code,您会发现字典是可选的,仅在达到可配置阈值时创建。 KeyedCollection 使用 List 来存储它的项目,它继承自 Collection
    • @EugeneBeresovsky - 谢谢,我明白了我的错误。我错过了 KeyedCollection 从 Collection 继承,并在添加项目时调用 base.InsertItem 。不幸的是,除非编辑答案,否则 StackOverflow(相当奇怪)缺乏删除反对票的方法。 :( 无论如何,我已经删除了我不正确的评论。
    【解决方案3】:

    有一个OrderedDictionary 类,它是一个字典,但可以按插入顺序索引,但它不是泛型的。目前.Net框架中还没有一个通用的。

    我从 .Net 团队的某个地方读到了一条评论,说他们可能在未来实现一个通用版本,但如果是这样,它很可能会被称为 IndexableDictionary 而不是OrderedDictionary 使其行为更加明显。

    编辑: 找到了引用。它位于 OrderedDictionary 的 MSDN 页面上,归功于 Microsoft 的 David M. Kean:

    这种类型实际上是错误命名的;它不是一个“有序”字典,而是一个“索引”字典。虽然,今天没有这种类型的等效通用版本,但如果我们将来添加一个,我们很可能会命名为“IndexedDictionary”类型。

    【讨论】:

    • 是的,文档也很混乱。它说元素没有以任何方式排序。如果它说集合保持时间顺序会更清楚。但是,要说元素根本没有排序让我想到了哈希表。
    【解决方案4】:

    这是 非泛型 Systems.Collections.Specialized.OrderedDictionary 类型的包装器。

    这种类型将按插入顺序返回键/值/对序列,很像 Ruby 2.0 哈希。

    它不需要 C#6 魔法,符合IDictionary&lt;TKey,TValue&gt;(这也意味着访问未分配的键会引发异常),并且应该是可序列化的。

    在 Adrian 的回答中,每条注释都将其命名为“IndexedDictionary”。

    YMMV。

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    
    /// <summary>
    /// A dictionary that maintains insertion ordering of keys.
    /// 
    /// This is useful for emitting JSON where it is preferable to keep the key ordering
    /// for various human-friendlier reasons.
    /// 
    /// There is no support to manually re-order keys or to access keys
    /// by index without using Keys/Values or the Enumerator (eg).
    /// </summary>
    [Serializable]
    public sealed class IndexedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        // Non-generic version only in .NET 4.5
        private readonly OrderedDictionary _backing = new OrderedDictionary();
    
        private IEnumerable<KeyValuePair<TKey, TValue>> KeyValuePairs
        {
            get
            {
                return _backing.OfType<DictionaryEntry>()
                    .Select(e => new KeyValuePair<TKey, TValue>((TKey)e.Key, (TValue)e.Value));
            }
        }
    
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return KeyValuePairs.GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    
        public void Add(KeyValuePair<TKey, TValue> item)
        {
            _backing[item.Key] = item.Value;
        }
    
        public void Clear()
        {
            _backing.Clear();
        }
    
        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return _backing.Contains(item.Key);
        }
    
        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            KeyValuePairs.ToList().CopyTo(array, arrayIndex);
        }
    
        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            TValue value;
            if (TryGetValue(item.Key, out value)
                && Equals(value, item.Value))
            {
                Remove(item.Key);
                return true;
            }
            return false;
        }
    
        public int Count
        {
            get { return _backing.Count; }
        }
    
        public bool IsReadOnly
        {
            get { return _backing.IsReadOnly; }
        }
    
        public bool ContainsKey(TKey key)
        {
            return _backing.Contains(key);
        }
    
        public void Add(TKey key, TValue value)
        {
            _backing.Add(key, value);
        }
    
        public bool Remove(TKey key)
        {
            var result = _backing.Contains(key);
            if (result) {
                _backing.Remove(key);
            }
            return result;
        }
    
        public bool TryGetValue(TKey key, out TValue value)
        {
            object foundValue;
            if ((foundValue = _backing[key]) != null
                || _backing.Contains(key))
            {
                // Either found with a non-null value, or contained value is null.
                value = (TValue)foundValue;
                return true;
            }
            value = default(TValue);
            return false;
        }
    
        public TValue this[TKey key]
        {
            get
            {
                TValue value;
                if (TryGetValue(key, out value))
                    return value;
                throw new KeyNotFoundException();
            }
            set { _backing[key] = value; }
        }
    
        public ICollection<TKey> Keys
        {
            get { return _backing.Keys.OfType<TKey>().ToList(); }
        }
    
        public ICollection<TValue> Values
        {
            get { return _backing.Values.OfType<TValue>().ToList(); }
        }
    }
    

    【讨论】:

    • 这与McAden's answer有何不同?
    • "它不需要 C#6 魔法,符合 IDictionary(这也意味着访问未分配的键会引发异常),并且应该是可序列化的。"
    • 不要求 C# 6 并符合 IDictionary&lt;TKey, TValue&gt; 的好处是什么?
    • 这些应该是解释性的.. 1) 不使用/需要 C#6(无论出于何种原因 - 是的,我 确实 处理此类代码); 2) 能够在预期/需要IDictionary&lt;TKey, TValue&gt; 的地方使用结果对象(考虑到即使ConcurrentDictionary&lt;..&gt; 实现IDictionary&lt;..&gt;
    【解决方案5】:

    code project 上有一个通用实现,它带有合理数量的测试用例。

    作者选择了一个相当有趣的名字(KeyedList),这使得它很难找到。

    【讨论】:

      【解决方案6】:

      保留插入的通用键/值对的另一个选项是使用类似的东西:

      Queue<KeyValuePair<string, string>>
      

      这将是一个有保证的有序列表。您可以在类似于添加/删除字典的有序派系中入队和出队,而不是调整数组的大小。它通常可以作为非调整大小的有序(通过插入)数组和自动调整大小的无序(通过插入)列表之间的中间地带。

      【讨论】:

      • 也可以使用List&lt;&gt;。这种方法的一大缺点是按键查找变得更加复杂。
      【解决方案7】:

      我知道您正在编写 C#,但 Java 有一个名为 LinkedHashMap 的类,它使用私有 LinkedList 来维护键的插入顺序。如果您找不到合适的通用解决方案,也许这将是实施您自己的解决方案的开始。

      【讨论】:

        【解决方案8】:

        如果您需要AddRemoveContainsKey 的恒定复杂性和订单保存,那么 .NET Framework 4.5 中没有这样的通用性。

        如果您对 3rd 方代码没问题,请查看我的存储库(许可 MIT 许可证): https://github.com/OndrejPetrzilka/Rock.Collections

        OrderedDictionary&lt;K,V&gt;收藏:

        • 源代码基于经典Dictionary&lt;K,V&gt;(来自.NET Core)
        • 保留插入顺序并允许手动重新排序
        • 特性反向枚举
        • Dictionary&lt;K,V&gt;具有相同的操作复杂性
        • Dictionary&lt;K,V&gt; 相比,AddRemove 的操作速度要慢约 20%
        • 每个项目多消耗 8 个字节的内存

        【讨论】:

          【解决方案9】:

          代码:

          //A SortedDictionary is sorted on the key (not value)
          System.Collections.Generic.SortedDictionary<string, string> testSortDic = new SortedDictionary<string, string>();
          
          //Add some values with the keys out of order
          testSortDic.Add("key5", "value 1");
          testSortDic.Add("key3", "value 2");
          testSortDic.Add("key2", "value 3");
          testSortDic.Add("key4", "value 4");
          testSortDic.Add("key1", "value 5"); 
          
          //Display the elements.  
          foreach (KeyValuePair<string, string> kvp in testSortDic)
          {
              Console.WriteLine("Key = {0}, value = {1}", kvp.Key, kvp.Value);
          }
          

          输出:

          Key = key1, value = value 5
          Key = key2, value = value 3
          Key = key3, value = value 2
          Key = key4, value = value 4
          Key = key5, value = value 1     
          

          【讨论】:

          • -1,OP 要求字典保留插入顺序而不保留按键排序
          猜你喜欢
          • 2011-01-23
          • 1970-01-01
          • 2012-03-24
          • 1970-01-01
          • 1970-01-01
          • 2014-12-17
          • 2010-10-14
          • 2012-12-12
          • 1970-01-01
          相关资源
          最近更新 更多