C#Distinct()方法是否保持序列的原始排序完整?

Nit*_*esh 76 c# list duplicates

我想从列表中删除重复项,而不更改列表中唯一元素的顺序.

Jon Skeet和其他人建议使用以下内容

list = list.Distinct().ToList();
Run Code Online (Sandbox Code Playgroud)

从列表C#中删除重复项

从C#中的List <T>中删除重复项

是否保证独特元素的顺序与以前相同?如果是,请提供一个确认的参考,因为我在文档中找不到任何内容.

Jon*_*eet 67

它不能保证,但它是最明显的实现.很难以流式方式实现(即,它尽可能快地返回结果,尽可能少地读取)而不按顺序返回它们.

您可能想阅读关于Distinct()Edulinq实现的博客文章.

请注意,即使LINQ to Objects(我个人觉得它应该是这样)保证这对于其他LINQ提供程序(例如LINQ to SQL)也没有任何意义.

在LINQ to Objects中提供的保证级别有时会有点不一致,IMO.记录了一些优化,其他则没有.哎呀,有些文件是错误的.


Ser*_*kiy 26

是的,按原始列表中第一次出现的顺序排列.它保证适用于.Net Framework 3.5

我用Reflector进行了一些调查.在反汇编System.Core.dll,版本= 3.5.0.0后,您可以看到Distinct()是一个扩展方法,如下所示:

public static class Emunmerable
{
    public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        return DistinctIterator<TSource>(source, null);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,这里有趣的是DistinctIterator,它实现了IEnumerable和IEnumerator.这是IEnumerator的简化(goto和lables删除)实现:

private sealed class DistinctIterator<TSource> : IEnumerable<TSource>, IEnumerable, IEnumerator<TSource>, IEnumerator, IDisposable
{
    private bool _enumeratingStarted;
    private IEnumerator<TSource> _sourceListEnumerator;
    public IEnumerable<TSource> _source;
    private HashSet<TSource> _hashSet;    
    private TSource _current;

    private bool MoveNext()
    {
        if (!_enumeratingStarted)
        {
            _sourceListEnumerator = _source.GetEnumerator();
            _hashSet = new HashSet<TSource>();
            _enumeratingStarted = true;
        }

        while(_sourceListEnumerator.MoveNext())
        {
            TSource element = _sourceListEnumerator.Current;

             if (!_hashSet.Add(element))
                 continue;

             _current = element;
             return true;
        }

        return false;
    }

    void IEnumerator.Reset()
    {
        throw new NotSupportedException();
    }

    TSource IEnumerator<TSource>.Current
    {
        get { return _current; }
    }

    object IEnumerator.Current
    {        
        get { return _current; }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的 - 枚举顺序由source enumerable提供(list,我们在其上调用Distinct).Hashset仅用于确定我们是否已经返回此类元素.如果没有,我们将返回它,否则 - 继续枚举源.

因此,可以保证,Distinct()将返回完全按相同顺序排列的元素,这些元素由应用了Distinct的集合提供.

  • @lazyberezovsky:当人们谈论担保时,他们通常意味着**记录的行为**这是合理的依赖.例如,GroupBy*的文档执行*指定行为,但Distinct*的文档不指定*. (38认同)
  • 这是一个记录良好的行为吗? (7认同)
  • @lazyberezovsky:我来自C\C++,其中很多东西都是未定义的,并且如果有必要保证它是很常见的.此外,我在Silverlight应用程序中使用Distinct(),这是在Mac和Windows上,这就是为什么我们不能满足'共同实现'它必须得到保证. (5认同)
  • 链接的答案包含对文档的引用,该文档说:"结果序列是无序的." (4认同)
  • @lazyberezovsky:问题是关于*保证*,而不是*常见的实施*.(正如我已经说过的,如果实现在平台/版本之间发生变化,我会感到惊讶,但这并不能保证.) (4认同)
  • 我的意思是,你能给出一个确认这个的链接或参考吗?文档没有说明任何内容. (2认同)

mgr*_*ber 13

根据文档,序列是无序的.

  • 其他信息以找到它:在链接中,请参阅“备注”部分。“结果序列是无序的。” (3认同)

Col*_*nic 6

是的,Enumerable.Distinct保留顺序。假设该方法是“懒惰的”,“一旦看到它们就会产生不同的值”,它会自动执行。想一想。

.NET参考源确认。它返回一个子序列,每个子类中的第一个元素。

foreach (TSource element in source)
    if (set.Add(element)) yield return element;
Run Code Online (Sandbox Code Playgroud)

.NET核心实现是类似的。

令人沮丧的是,Enumerable.Distinct的文档在这一点上感到困惑:

结果序列是无序的。

我只能想象它们的意思是“结果序列未排序”。您可以通过预排序然后将每个元素与前一个元素进行比较实现Distinct,但这并不像上面定义的那样懒惰。

  • 来源不是规格。您发现的是一个巧合,在下一次更新后可能无效。 (4认同)

Pet*_*ore 5

有点晚了,但没有人真正发布了完成此 IMO 的最佳完整代码,所以让我提供这个(这与 .NET Framework 使用 Distinct() 所做的基本上相同)*:

    public static IEnumerable<T> DistinctOrdered<T>(this IEnumerable<T> items)
    {
        HashSet<T> returnedItems = new HashSet<T>();
        foreach (var item in items)
        {
            if (returnedItems.Add(item))
                yield return item;
        }                       
    }
Run Code Online (Sandbox Code Playgroud)

这保证了原始订单,而不依赖于未记录或假设的行为。我还相信这比使用多个 LINQ 方法更有效,尽管我愿意在这里纠正。

(*) .NET Framework 源使用内部Set类,它看起来与HashSet.

  • 这并不重要,因为顺序将由所提供的枚举决定。HashSet 只是告诉我们该项目是否已返回,如果是则跳过它。 (3认同)