为什么HashSet<T>class不是用来实现Enumerable.Distinct

Why HashSet<T> class is not used to implement Enumerable.Distinct

我需要访问渐近时间和 space 大 O 表示法 IEnumerable.Distinct 的复杂度

所以我在看扩展方法 Enumerable.Distinct and I see it is implemented using and internal class Set<T> 的实现,这几乎是散列 table 和 "open addressing"

的经典实现

很快就吸引眼球的是 Set<T> is just a copy-paste from HashSet<T> 中有很多代码,还有一些遗漏

然而,这简化了 Set<T> implementation has some obvious flaws, for example the Resize method not using prime numbers for the size of the slots, like HashSet<T> does, see HashHelpers.ExpandPrime

所以,我的问题是:

  1. 这里代码重复的原因是什么,为什么不坚持DRY原则呢?特别是考虑到这两个 类 都在同一个程序集中 System.Core
  2. 看起来像HashSet<T> will perform better, so should I avoid using Distinct extension method, and write my own extension method that would use HashSet<T> instead of Set<T>?

which is almost a classical implementation of a hash table with "open addressing"

再看看。它与列表头单元格分开链接。虽然插槽都在一个数组中,但在发生冲突的情况下查找下一个插槽是通过检查当前插槽的 next 字段来完成的。这比使用每个节点作为单独堆对象的链表具有更好的缓存效率,尽管在这方面不如开放式寻址。同时也避免了一些开放寻址效果不好的情况。

a lot of code in Set is just a copy-paste from HashSet, with some omissions

AFAICT 使用 hash-set 的私有实现的原因是 EnumerableHashSet 大约在同一时间独立开发。这只是我的推测,但它们都是在 .NET 3.5 中引入的,所以它是可行的。

很可能 HashSet<T> 是从复制 Set<T> 开始的,然后让它更好地服务于公开展示,尽管也有可能两者都基于相同的独立链接原则列出头部单元格

在性能方面,HashSet使用质数意味着它更有可能避免与不良哈希值的冲突(但究竟有多大优势,这不是一个简单的问题),但是Set 在很多方面都更轻巧,尤其是在 .NET Core 中,其中删除了一些不需要的东西。特别是,Set 的那个版本利用了一个事实,即一旦一个项目被删除(例如,在 Intersect 期间发生),就永远不会添加一个项目,这允许它遗漏freelist 以及与之相关的任何工作,HashSet 无法完成。即使是初始实施也更轻松,因为不跟踪版本以在枚举期间捕获更改,这是一个很小的成本,但是每次添加和删除都会有成本。

因此,对于具有不同散列码分布的不同数据集,有时一个表现更好,有时另一个表现更好。

Especially given the fact that both of these classes are in the same assembly System.Core

仅在某些版本的 .NET 中,在某些版本中它们位于单独的程序集中。在 .NET Core 中,我们有两个版本的 Set<T>,一个在具有 System.Linq 的程序集中,另一个在具有 System.Linq.Expressions 的单独程序集中。前者如上所述被削减,后者被 HashSet<T> 取代,因为它在那里做得更少。

当然 System.Core 排在第一位,但这些元素完全可以分离出来的事实说明 System.Core 不是 inter-dependencies.[=40 的单一整体块=]

.NET Core 版本的 Linq 中现在有一个 ToHashSet() 方法,这使得用 HashSet<T> 替换 Set<T> 的可能性更加合理,尽管不是 no-brainer .我认为@james-ko 正在考虑测试这样做的好处。

It looks like HashSet<T> will perform better

出于上述原因,情况可能并非如此,但可能确实如此,具体取决于源数据。那是在考虑跨几种不同 linq 方法的优化之前(在 linq 的初始版本中不多,但在 .NET Core 中有很多)。

so should I avoid using Distinct extension method, and write my own extension method that would use HashSet<T> instead of Set<T>.

使用Distinct()。如果您遇到瓶颈,那么 HashSet<T> 可能会在给定的 data-set 下获胜,但如果您尝试这样做,请确保您的分析与您的代码在现实生活中遇到的实际值非常匹配。如果您的应用程序遇到另一种方法做得更好的情况,那么根据一些任意测试来决定一种方法更快是没有意义的。 (如果我发现这是一个问题点,我会先看看所讨论的类型的 GetHashCode() 是否可以改进速度或位分布)。