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

时间:2011-01-19 11:47:32

标签: c# list duplicates

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

Jon Skeet&其他人建议使用以下

list = list.Distinct().ToList();

removing duplicates from a list C#

Remove duplicates from a List<T> in C#

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

6 个答案:

答案 0 :(得分:61)

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

您可能想在Edulinq implementation of Distinct()上阅读我的博文。

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

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

答案 1 :(得分:25)

是,按原始列表中第一次出现的顺序排列。 .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);
    }
}

所以,这里有趣的是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; }
    }
}

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

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

答案 2 :(得分:11)

根据documentation,序列是无序的。

答案 3 :(得分:4)

,Enumerable.Distinct会保留订单。假设方法是懒惰的“在看到它们时很快产生不同的值”,它会自动跟随。想一想。

.NET Reference source确认。它返回一个子序列,即每个等价类中的第一个元素。

foreach (TSource element in source)
    if (set.Add(element)) yield return element;

.NET Core implementation类似。

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

  

结果序列是无序的。

我只能想象他们的意思是“结果序列没有排序”。你可以通过预先分类然后将每个元素与前一个元素进行比较来实现Distinct,但这不会像上面定义的那样延迟。

答案 4 :(得分:1)

默认情况下,使用Distinct linq运算符时使用Equals方法,但您可以使用自己的IEqualityComparer<T>对象指定两个对象何时与实现GetHashCodeEquals方法的自定义逻辑相等。 请记住:

GetHashCode不应该使用繁重的cpu比较(例如,仅使用一些明显的基本检查),并且如果两个对象肯定不同(如果返回不同的哈希代码)或者可能相同的话,它首先用作状态(相同的哈希码)。在最新的情况下,当两个对象具有相同的哈希码时,框架将逐步检查使用Equals方法作为关于给定对象相等性的最终决定。

在您拥有MyTypeMyTypeEqualityComparer个类后,请遵循代码,确保序列保持其顺序:

var cmp = new MyTypeEqualityComparer();
var lst = new List<MyType>();
// add some to lst
var q = lst.Distinct(cmp);

在关注sci library中,我实施了一种扩展方法,以确保Vector3D集在使用特定扩展方法时保持顺序DistinctKeepOrder

相关代码如下:

/// <summary>
/// support class for DistinctKeepOrder extension
/// </summary>
public class Vector3DWithOrder
{
    public int Order { get; private set; }
    public Vector3D Vector { get; private set; }
    public Vector3DWithOrder(Vector3D v, int order)
    {
        Vector = v;
        Order = order;
    }
}

public class Vector3DWithOrderEqualityComparer : IEqualityComparer<Vector3DWithOrder>
{
    Vector3DEqualityComparer cmp;

    public Vector3DWithOrderEqualityComparer(Vector3DEqualityComparer _cmp)
    {
        cmp = _cmp;
    }

    public bool Equals(Vector3DWithOrder x, Vector3DWithOrder y)
    {
        return cmp.Equals(x.Vector, y.Vector);
    }

    public int GetHashCode(Vector3DWithOrder obj)
    {
        return cmp.GetHashCode(obj.Vector);
    }
}

简而言之Vector3DWithOrder封装了类型和顺序整数,而Vector3DWithOrderEqualityComparer封装了原始类型比较器。

这是确保维护订单的方法助手

/// <summary>
/// retrieve distinct of given vector set ensuring to maintain given order
/// </summary>        
public static IEnumerable<Vector3D> DistinctKeepOrder(this IEnumerable<Vector3D> vectors, Vector3DEqualityComparer cmp)
{
    var ocmp = new Vector3DWithOrderEqualityComparer(cmp);

    return vectors
        .Select((w, i) => new Vector3DWithOrder(w, i))
        .Distinct(ocmp)
        .OrderBy(w => w.Order)
        .Select(w => w.Vector);
}

注意:进一步的研究可以允许找到更一般的(使用接口)和优化的方式(不封装对象)。

答案 5 :(得分:0)

这在很大程度上取决于您的linq提供者。在Linq2Objects上,您可以停留在Distinct的内部源代码上,这使我们假设保留了原始顺序。

例如,对于其他解析为某种SQL的提供程序而言,则不一定如此,因为ORDER BY语句通常在任何聚合之后出现(例如Distinct)。因此,如果您的代码是这样的:

myArray.OrderBy(x => anothercol).GroupBy(x => y.mycol);

这被翻译成类似于SQL中的以下内容:

SELECT * FROM mytable GROUP BY mycol ORDER BY anothercol;

显然,这首先会对您的数据进行分组,然后对其进行排序。现在,您被DBMS自己如何执行的逻辑所束缚。在某些DBMS上甚至不允许这样做。想象以下数据:

mycol anothercol
1     2
1     1
1     3
2     1
2     3

在执行myArr.OrderBy(x => x.anothercol).GroupBy(x => x.mycol)时,我们得出以下结果:

mycol anothercol
1     1
2     1

但是DBMS可能会聚合另一个列,因此始终使用第一行的值,从而得到以下数据:

mycol anothercol
1    2
2    1

订购后将导致以下结果:

mycol anothercol
2    1
1    2

这类似于以下内容:

SELECT mycol, First(anothercol) from mytable group by mycol order by anothercol;

这是完全相反的顺序。

您看到执行计划可能会有所不同,具体取决于基础提供程序是什么。这就是为什么文档中对此没有保证的原因。