是否可以在LINQ-to-objects中保存顺序?

时间:2011-08-31 15:38:05

标签: c# .net linq linq-to-objects

var source = new List<string> { "A1", "A2", "B1", "B2" };
var filtered = source.Where(s => s.StartsWith("A"));

foreach (var s in filtered)
    Console.WriteLine(s);    // outputs first A1 and then A2

似乎Enumerable.Where一样,在有序的IEnumerable(例如List<T>T[])上使用时保留元素的原始顺序。总是这样吗?如果是的话,这会记录在哪里?

10 个答案:

答案 0 :(得分:7)

Microsoft确实记录了LINQ to Objects保留排序。该文件 http://msdn.microsoft.com/en-us/library/dd460677%28v=vs.110%29.aspx

  

在PLINQ中,目标是在维护时最大限度地提高性能   正确性。查询应该尽可能快地运行但仍然生成   正确的结果。在某些情况下,正确性需要的顺序   要保留的源序列;但是,订购可以   计算上很昂贵。因此,默认情况下,PLINQ不会   保留源序列的顺序。在这方面,PLINQ   类似于LINQ to SQL,但不像 LINQ to Objects那样   保留订购。

正如this stackoverflow article微软文档中提到的一些LINQ方法,它们不保留顺序。例如,documentation of distinct提到此方法返回无序序列。

答案 1 :(得分:6)

使用Enumerable.Where方法保存订单。

在SO上有一个类似的问题,第一个答案分解了哪些方法保留了顺序:

Preserving order with LINQ

答案 2 :(得分:1)

没有记录。 LINQ运算符的文档在很多方面都非常缺乏,包括何时何时不保持顺序,何时何时不是缓冲操作,或者复杂性保证是什么。

在这种情况下,我不介意依赖于实现保持顺序,因为这就是所有实际实现的功能。

我建议您阅读Jon Skeet撰写的Edulinq系列文章,在实施该功能之前,他会解释您应该做什么以及您不应该对操作员有什么期望。

答案 3 :(得分:1)

Enumerable.Where将保留IEnumerable<T>的顺序。这没有记录,因此不是保证,但是,实现只是按顺序迭代IEnumerable<T> - 实际上是保留顺序 - 尽管该顺序依赖于基础IEnumerable<T>的顺序。 (例如,HashSet<T>上的位置未被排序,但这是因为HashSet<T>的可枚举是无序的。)

但是,它确实包含了处理某些类型(例如数组和List<T>)的特殊实现,因此特定的集合类型在将来的某个时候可能会返回不同的结果并非不可能如果这被认为是速度/性能/等方面的有价值的改进。文档从未明确提供订购保证。

答案 4 :(得分:1)

Where实现基本上如下,并在:

中添加了一些额外的变量检查
public static IEnumerable<T> Where(this IEnumerable<T> source, Funct<T, bool> predicate)
{
   foreach (T item in source)
       if (predicate(item))
           yield return item;
}

因此,假设产量不是异步或并行完成的,它将保留订单。如果您的来源是.AsParallel,则所有投注都会按顺序排除。

答案 5 :(得分:1)

LINQ的反编译是:

private static IEnumerable<TSource> WhereIterator<TSource>(IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
{
    int num = -1;
    IEnumerator<TSource> getenumerator = source.GetEnumerator();
    while (getenumerator.MoveNext())
    {
        TSource current = getenumerator.Current;
        num++;
        if (predicate(current, num))
        {
            yield return current;
        }
    }
}

System.Collections.Generic.List.MoveNext()的反编译具有代码:

if (this.version == ts._version && this.index < ts._size)
{
    this.current = ts._items[this.index];
    this.index = this.index + 1;
    return true;
}

将这两者结合使用,您可以看到订单将被保留。当然,微软可能在未来改变它,但基于.NET 4.0,List.Where将被订购。

答案 6 :(得分:0)

它似乎必须保留顺序,因为它能够在无限IEnumerable s上运行。

void Main() {
    foreach (var element in Count().Where (i => i%2 == 1)) {
        // do something with all odd numbers
    }
}

IEnumerable<int> Count() {
    int i = 0;
    while (true) yield return ++i;
}

我无法在任何地方找到这个记录。

答案 7 :(得分:0)

是的,它将根据迭代器进行排序。由于这不是PLINQ,因此可枚举按可枚举元素位置的顺序迭代 - 因此,如果您为类实现了某种自定义IEnumerator,它将以该顺序返回。

答案 8 :(得分:0)

Enumerable.Where遵循通过在底层集合上调用GetEnumerator生成的枚举器返回的元素的顺序 - 查看MSDN List<T>上的枚举器被记录为遵循索引行为。

答案 9 :(得分:0)

如果不提供明确的OrderBy,则Where方法无法保证订单 - 否则,它会被记录为执行此操作。