关于c#:通用键/值对集合中保留了插入顺序?

Generic Key/Value pair collection in that preserves insertion order?

不过,我在找字典之类的东西,以保证它能保持插入顺序。因为字典是哈希表,所以我不认为它是哈希表。

是否有用于此的常规集合,或者是否需要使用旧的.NET 1.1集合之一?


没有。但是,System.Collections.Specialized.OrderedDictionary应该解决对它的大多数需求。

编辑:另一个选项是将其转换为通用的。我还没有测试过它,但是它编译了(C 6),应该可以工作。然而,它仍然具有与Ondrej Petrzilka在下面的评论中提到的相同的限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    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);
    }


实际上有一个是通用的,从.NET 2.0开始就存在。它被称为">KeyedCollection"。但是,它附带的限制是从值构造键,因此它不是通用的键/值对集合。(当然,你也可以像KeyedCollection>那样使用它作为解决方案)。

如果您需要它作为一个IDictionary,它有一个.Dictionary属性。

我对它有一个小问题,那就是它是一个抽象类,您必须对它进行子类化并实现:

1
protected abstract TKey GetKeyForItem(TItem item)

为此,我宁愿将lambda传递到构造函数中,但同样地,我猜虚拟方法比lambda稍快一些(任何关于此的评论都值得赞赏)。

根据评论中提出的问题进行编辑:KeyedCollection保留了秩序,因为它继承了Collection,而IList继承了秩序。另请参见添加方法的文档:将对象添加到集合的末尾)。


有一个orderedDictionary类,它是一个字典,但可以按插入顺序进行索引,但它不是通用的。目前.NET框架中没有一个通用的。

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

编辑:找到报价单。它出现在OrderedDictionary的msdn页面上,归因于微软的david m.kean:

This type is actually misnamed; it is not an 'ordered' dictionary as such, but rather an 'indexed' dictionary. Although, today there is no equivalent generic version of this type, if we add one in the future it is likely that we will name such as type 'IndexedDictionary'.


好吧,你可以用一个List>,它可以维持秩序…但是,您将丢失字典的查找功能。你为什么要保留订单?


这里是非通用Systems.Collections.Specialized.OrderedDictionary类型的包装器。

这种类型将按插入顺序返回键/值/对序列,很像Ruby2.0散列。

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

在阿德里安的答案中,它的名字是"indexedDictionary"。

YMMV。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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(); }
    }
}


在代码项目上有一个通用的实现,它附带了大量的测试用例。

作者选择了一个相当有趣的名字(keyedlist),很难找到。


如果您需要持续复杂的AddRemoveContainsKey和顺序保存,那么在.NET框架4.5中就没有这样的通用性。

如果您对第三方代码还满意,请查看我的存储库(许可的MIT许可证):https://github.com/ondrejpetrozilka/rock.collections网站

OrderedDictionary号藏品:

  • 基于经典Dictionary的源代码(来自.NET核心)
  • 保留插入顺序并允许手动重新排序
  • 功能反向枚举
  • Dictionary具有相同的操作复杂性
  • AddRemove的操作比Dictionary慢约20%。
  • 每个项目多消耗8个字节的内存

保留插入的通用键/值对的另一个选项是使用如下内容:

1
Queue<KeyValuePair<string, string>>

这将是一份有保证的订购清单。您可以在一个类似于添加/删除字典而不是调整数组大小的有序区域中排队和出列。它通常可以充当非调整大小的有序(按插入)数组和自动调整大小的无序(按插入)列表之间的中间地带。


我知道你写的是C,但是Java有一个叫做Link KeHHMAP的类,它使用一个私有链接表来维护密钥插入的顺序。如果您找不到合适的通用解决方案,也许这将是您自己实现的开始。


代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//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);
}

输出:

1
2
3
4
5
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