没有 OrderedDictionary 的通用实现?
.NET 3.5 中似乎没有 OrderedDictionary
(位于 System.Collections.Specialized
命名空间中)的通用实现。我还缺少一个吗?
我已经找到了提供该功能的实现,但想知道是否/为什么没有开箱即用的通用实现,以及是否有人知道它是否是 .NET 4.0 中的东西?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
实现通用的
OrderedDictionary
并不是非常困难,但它不必要地耗费时间,坦率地说,这个类是 Microsoft 的一个巨大疏忽。有多种方法可以实现此目的,但我选择使用KeyedCollection
作为我的内部存储。我还选择实现各种方法来按照List
的方式进行排序,因为这本质上是 IList 和 IDictionary 的混合体。我已将我的实现包含在此处以供后代使用。这是界面。请注意,它包括
System.Collections.Specialized.IOrderedDictionary
,它是 Microsoft 提供的此接口的非通用版本。这是实现以及辅助类:
如果没有一些测试,任何实现都是不完整的(但悲惨的是,所以不允许我在一篇文章中发布那么多代码),所以我不得不让你编写测试。但是,我留下了其中的一些内容,以便您可以了解它是如何工作的:
--更新--
此库和其他真正有用的缺失核心 .NET 库的来源:https://github.com/mattmc3/dotmore/blob/master /dotmore/Collections/Generic/OrderedDictionary.cs
更新要点链接:https://gist. github.com/mattmc3/6889f6b3fb1b5d74ebec7883f5825ea1
Implementing a generic
OrderedDictionary
isn't terribly difficult, but it's unnecessarily time consuming and frankly this class is a huge oversight on Microsoft's part. There are multiple ways of implementing this, but I chose to use aKeyedCollection
for my internal storage. I also chose to implement various methods for sorting the way thatList<T>
does since this is essentially a hybrid IList and IDictionary. I've included my implementation here for posterity.Here's the interface. Notice that it includes
System.Collections.Specialized.IOrderedDictionary
, which is the non-generic version of this interface that was provided by Microsoft.Here's the implementation along with helper classes:
And no implementation would be complete without a few tests (but tragically, SO won't let me post that much code in one post), so I'll have to leave you to write your tests. But, I left a few of them in so that you could get an idea of how it works:
-- UPDATE --
Source for this and other really useful missing core .NET libraries here: https://github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrderedDictionary.cs
Updated gist link: https://gist.github.com/mattmc3/6889f6b3fb1b5d74ebec7883f5825ea1
你说得对。框架本身没有
OrderedDictionary
的通用等效项。(据我所知,.NET 4 也是如此。)
You're right. There's no generic equivalent of
OrderedDictionary
in the framework itself.(That's still the case for .NET 4 too, as far as I'm aware.)
根据记录,有一个通用的 KeyedCollection 允许对对象建立索引通过一个 int 和一个键。键必须嵌入到值中。
For the record, there is a generic KeyedCollection that allows objects to be indexed by an int and a key. The key must be embedded in the value.
这是一个奇怪的发现:System.Web.Extensions.dll 中的
System.Web.Util
命名空间包含一个通用的OrderedDictionary
不知道为什么 MS 将它放在那里而不是 System.Collections.Generic 包,但我假设您可以简单地复制粘贴代码并使用它(它是内部的,所以不能直接使用它)。看起来该实现使用了标准字典和单独的键/值列表。非常简单...
Here's a bizarre find: the
System.Web.Util
namespace in System.Web.Extensions.dll contains a genericOrderedDictionary<TKey,TValue>
Not sure why MS placed it there instead of the System.Collections.Generic package, but I assume you can simply copy paste the code and use it (it's internal, so can't use it directly). Looks like the implementation uses a standard dictionary and separate Key/Value lists. Pretty straightforward...
System.Runtime.Collections
that wraps the non-genericSystem.Collections.Specialized.OrderedDictionary
: https://referencesource.microsoft.com/#System.ServiceModel.Internals/System/Runtime/Collections/OrderedDictionary.cs对于它的价值,这是我解决它的方法:
它可以像这样初始化:
并像这样访问:
For what it's worth, here is how I solved it:
It can be initialized like this:
and accessed like this:
OrderedDictionary
的通用版本的一个主要概念问题是OrderedDictionary
的用户希望能够使用OrderedDictionary
对其进行数字索引>int,或使用TKey
进行查找。当唯一的键类型是Object
时,就像非泛型OrderedDictionary
的情况一样,传递给索引器的参数类型足以区分什么类型应执行索引操作。但实际上,尚不清楚OrderedDictionary
的索引器应如何运行。如果像
Drawing.Point
这样的类建议并遵循分段可变结构应将其可变元素公开为字段而不是属性的规则,并且避免使用修改this
,然后OrderedDictionary
可以有效地公开一个ByIndex
属性,该属性返回一个Indexer
结构,该结构保存对字典的引用,并且有一个索引属性,其 getter 和 setter 将对其调用GetByIndex
和SetByIndex
。因此,可以说类似MyDict.ByIndex[5] += 3;
将 3 添加到字典的第六个元素。不幸的是,为了让编译器接受这样的事情,有必要让
ByIndex
属性在每次调用时返回一个新的类实例而不是一个结构体,从而消除了通过避免装箱获得的优势。在 VB.NET 中,可以通过使用命名索引属性来解决该问题(因此
MyDict.ByIndex[int]
将成为MyDict
的成员,而不需要 < code>MyDict.ByIndex 成为包含索引器的MyDict
的成员),但 C# 不允许这样的事情。提供一个 OrderedDictionary 可能仍然是值得的。其中 TKey:class,但首先提供泛型的大部分原因是允许它们与值类型一起使用。
A major conceptual problem with a generic version of
OrderedDictionary
is that users of aOrderedDictionary<TKey,TValue>
would expect expect to be able to index it either numerically using anint
, or by lookup using aTKey
. When the only type of key wasObject
, as was the case with non-genericOrderedDictionary
, the type of argument passed to the indexer would be sufficient to distinguish whether what type of indexing operation should be performed. As it is, though, it's unclear how the indexer of anOrderedDictionary<int, TValue>
should behave.If classes like
Drawing.Point
had recommended and followed a rule that piecewise-mutable structures should expose their mutable elements as fields rather than properties, and refrain from using property setters that modifythis
, then anOrderedDictionary<TKey,TValue>
could efficiently expose aByIndex
property that returned anIndexer
struct which held a reference to the dictionary, and had an indexed property whose getter and setter would callGetByIndex
andSetByIndex
upon it. Thus, one could say something likeMyDict.ByIndex[5] += 3;
to add 3 to the sixth element of the dictionary.Unfortunately, for the compiler to accept such a thing, it would be necessary to make the
ByIndex
property return a new class instance rather than a struct every time it's invoked, eliminating the advantages one would get by avoiding boxing.In VB.NET, one could get around that issue by using a named indexed property (so
MyDict.ByIndex[int]
would be a member ofMyDict
, rather than requiringMyDict.ByIndex
to be a member ofMyDict
which includes an indexer), but C# doesn't allow such things.It might still have been worthwhile to offer an
OrderedDictionary<TKey,TValue> where TKey:class
, but much of the reason for providing generics in the first place was to allow their use with value types.对于很多目的,我发现可以使用
List>
来实现。 (显然,如果您需要它来扩展Dictionary
,则不需要,如果您需要比 O(n) 更好的键值查找,则不需要。)For a lot of purposes I've found one can get by with a
List<KeyValuePair<K, V>>
. (Not if you need it to extendDictionary
, obviously, and not if you need better than O(n) key-value lookup.)对于那些在 NuGet 中寻找“官方”包选项的人来说,通用 OrderedDictionary 的实现已被 .NET CoreFX 实验室接受。如果一切顺利,该类型最终将获得批准并集成到主 .NET CoreFX 存储库中。
此实现有可能会被拒绝。
可以在此处引用已提交的实现
https://github .com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/OrderedDictionary.cs
可以在此处找到明确可供使用的此类型的 NuGet 包
https://www.nuget.org/packages/Microsoft .Experimental.Collections/1.0.6-e190117-3
或者您可以在 Visual Studio 中安装该包。浏览包“Microsoft.Experimental.Collections”并确保选中“包括预发布”复选框。
如果该类型正式可用,我们将更新这篇文章。
For those looking for an "official" package option in NuGet, an implementation of a generic OrderedDictionary has been accepted into .NET CoreFX Lab. If all goes well, the type will eventually be approved and integrated to the main .NET CoreFX repo.
There is a possibility that this implementation will be rejected.
The committed implementation can be referenced here
https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/OrderedDictionary.cs
The NuGet package that definitively has this type available for use can be found here
https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3
Or you can install the package within Visual Studio. Browse for the package "Microsoft.Experimental.Collections" and make sure the "Include prerelease" checkbox is selected.
Will update this post if and when the type is made officially available.
是的,这是一个不幸的遗漏。我怀念 Python 的 OrderedDict
所以我用 C# 编写了自己的
OrderedDictionary
类。它是如何运作的?它维护两个集合 - 一个普通的无序字典和一个有序的键列表。通过这个解决方案,标准字典操作保持了其快速的复杂性,并且通过索引查找也很快。https://gist.github.com/hickford/5137384
这是界面
Right, it's an unfortunate omission. I miss Python's OrderedDict
So I wrote my own
OrderedDictionary<K,V>
class in C#. How does it work? It maintains two collections - a vanilla unordered dictionary and an ordered list of keys. With this solution, the standard dictionary operations keep their fast complexities, and look up by index is fast too.https://gist.github.com/hickford/5137384
Here's the interface
有
SortedDictionary
。尽管在语义上很接近,但我并不是说它与 OrderedDictionary 相同,因为它们并非如此。即使从性能特征来看。然而,Dictionary
(以及在某种程度上OrderedDictionary
和答案中提供的实现)和SortedDictionary
之间非常有趣且非常重要的区别是后者在下面使用二叉树。这是至关重要的区别,因为它使类不受应用于泛型类的内存约束的影响。请参阅此线程,了解使用泛型类处理大量键值对时抛出的OutOfMemoryExceptions
。如何计算最大值传递给 Dictionary 构造函数以避免 OutOfMemoryException 的容量参数值?
There is
SortedDictionary<TKey, TValue>
. Although semantically close, I am not claiming it's the same asOrderedDictionary
simply because they are not. Even from performance characteristics. However the very interesting and quite important difference betweenDictionary<TKey, TValue>
(and to that extentOrderedDictionary
and implementations provided in answers) andSortedDictionary
is that the latter is using binary tree underneath. This is critical distinction because it makes the class immune to memory constraints applied to generic class. See this thread aboutOutOfMemoryExceptions
thrown when generic class is used for handling large set of key-value pairs.How to figure out the max value for capacity parameter passed to Dictionary constructor to avoid OutOfMemoryException?
作为 @VB 评论的后续,这里有一个可访问的实现 System.Runtime.Collections.OrderedDictionary<,>。我最初打算通过反射访问它并通过工厂提供它,但是它所在的 dll 似乎根本无法访问,所以我只是提取源代码本身。
需要注意的一件事是这里的索引器不会抛出
KeyNotFoundException
。我绝对讨厌那个约定,这是我在这个实现中所采取的第一个自由。如果这对您很重要,只需替换return default(TValue);
行即可。使用 C# 6(与 Visual Studio 2013 兼容)GitHub 上接受的拉取请求/讨论
As a follow up to the comment from @V.B. here's an accessible implementation of the System.Runtime.Collections.OrderedDictionary<,>. I was originally going to access it by reflection and provide it via a factory but the dll this is in does not seem to be very accessible at all so I just pulled the source itself.
One thing to note is the indexer here will not throw
KeyNotFoundException
. I absolutely hate that convention and that was the 1 liberty i took in this implementation. If that's important to you, just replace the line forreturn default(TValue);
. Uses C# 6 (compatible with Visual Studio 2013)Pull requests/discussion accepted on GitHub
我通过环绕
SortedList
并添加私有Dictionary
实现了通用OrderedDictionary
_order
。然后,我创建了Comparer
的内部实现,传递对 _order 字典的引用。然后我将此比较器用于内部 SortedList。此类保留传递给构造函数的元素的顺序以及添加的顺序。此实现具有与
SortedList
几乎相同的大 O 特性,因为添加和删除 _order 的时间复杂度为 O(1)。每个元素将占用(根据《C# 4 in a Nutshell》一书,第 292 页,表 7-1)额外的内存空间 22(开销)+ 4(int order)+ TKey 大小(假设 8)= 34加上SortedList
的两个字节的开销,总开销是36字节,而同一本书上说非泛型OrderedDictionary
有开销。 59 字节。如果我将
sorted=true
传递给构造函数,则根本不使用 _order,OrderedDictionary
正是SortedList< /code> 如果有意义的话,包装的开销很小。
我将在
OrderedDictionary
中存储不多的大型引用对象,所以对我来说这个 ca. 36 字节的开销是可以接受的。主要代码如下。完整的更新代码位于此 gist 上。
I implemented a generic
OrderedDictionary<TKey, TValue>
by wraping aroundSortedList<TKey, TValue>
and adding a privateDictionary<TKey, int>
_order
. Then I created an internal implementation ofComparer<TKey>
, passing a reference to the _order dictionary. Then I use this comparer for the internal SortedList. This class keeps the order of elements passed to the constructor and order of additions.This implementation has almost the same big O characteristics as
SortedList<TKey, TValue>
since adding and removing to _order is O(1). Each element will take (according to the book 'C# 4 in a Nutshell', p. 292, table 7-1) additional memory space of 22 (overhead) + 4 (int order) + TKey size (let's assume 8) = 34. Together withSortedList<TKey, TValue>
's overhead of two bytes, the total overhead is 36 bytes, while the same book says that non-genericOrderedDictionary
has an overhead of 59 bytes.If I pass
sorted=true
to constructor, then _order is not used at all, theOrderedDictionary<TKey, TValue>
is exactlySortedList<TKey, TValue>
with minor overhead for wrapping, if at all meaningful.I am going to store not-so-many large reference objects in the
OrderedDictionary<TKey, TValue>
, so for me this ca. 36 bytes overhead is tolerable.The main code is below. The complete updated code is on this gist.
这不是
OrderedDictionary<,>
的另一个版本/解决方案,而是我测试答案中提到的 4 个版本中的每一个的实验:@Colonel Panic、@mattmc3、@VB @Chris Marisic 。这是一种反馈。好吧,部分是因为我必须承认我没有剖析代码,所以功能或安全检查可能存在差异。但我仍然认为反馈对他们的表现很有用。正如您将看到的,时间可以从几毫秒到一刻钟。然后我用 O(n) 搜索编写了一个简单的最小版本,其中包含 2 个键和值类对象列表,只是为了看看 O(1) 访问的好处的大小。
测试平台是带有 Unity 3D 的 Microsoft Visual Studio Community 2019,每次测试连续 4 次,我想在其中复制真实场景的代码是
请注意,测试至少针对初始版本的最坏情况场景,因为它从索引 0 到迭代遍历集合,并且搜索是从结束到开始完成的。我以毫秒为单位测量了包含 50000 个条目的字典的
Add()
、ContainsKey()
和Remove()
。结果:
This is not yet another version/solution of an
OrderedDictionary<,>
but an experiment I did testing each of 4 versions mentioned in the answers: of @Colonel Panic, @mattmc3, @V.B. @Chris Marisic. It is meant as a feedback. Well, partial because I have to admit I haven't dissected the code, so there may be differences in functionality or safety checks. But still, I thought feedback would be useful on their performance. And as you'll see time can get from a couple of milliseconds to a quarter of hour.Then I scribbled a naive minimal version with 2 lists of key and value class objects with O(n) search just to see the magnitude of the benefit of O(1) access.
Testbed is Microsoft Visual Studio Community 2019 with Unity 3D, 4 consecutive times for each test and the code that I wanted to replicate a real-ish scenario in is
Note that the tests are for worst case scenarios in the case of naive version at least, as it iterates through the collection from index 0 through
iterations
and searching is done from end to start. I measuredAdd()
,ContainsKey()
andRemove()
in milliseconds for a dictionary of 50000 entries.Results:
.NET 9 将包含一个通用的 OrderedDictionary。这是合并的拉取请求: https://github.com/dotnet/runtime/pull/103309< /a>
.NET 9 will include a generic OrderedDictionary. Here’s the merged pull request: https://github.com/dotnet/runtime/pull/103309