ChatGPT解决这个技术问题 Extra ChatGPT

Preserving order with LINQ

I use LINQ to Objects instructions on an ordered array. Which operations shouldn't I do to be sure the order of the array is not changed?


A
Amy B

I examined the methods of System.Linq.Enumerable, discarding any that returned non-IEnumerable results. I checked the remarks of each to determine how the order of the result would differ from order of the source.

Preserves Order Absolutely. You can map a source element by index to a result element

AsEnumerable

Cast

Concat

Select

ToArray

ToList

Preserves Order. Elements are filtered or added, but not re-ordered.

Distinct

Except

Intersect

OfType

Prepend (new in .net 4.7.1)

Skip

SkipWhile

Take

TakeWhile

Where

Zip (new in .net 4)

Destroys Order - we don't know what order to expect results in.

ToDictionary

ToLookup

Redefines Order Explicitly - use these to change the order of the result

OrderBy

OrderByDescending

Reverse

ThenBy

ThenByDescending

Redefines Order according to some rules.

GroupBy - The IGrouping objects are yielded in an order based on the order of the elements in source that produced the first key of each IGrouping. Elements in a grouping are yielded in the order they appear in source.

GroupJoin - GroupJoin preserves the order of the elements of outer, and for each element of outer, the order of the matching elements from inner.

Join - preserves the order of the elements of outer, and for each of these elements, the order of the matching elements of inner.

SelectMany - for each element of source, selector is invoked and a sequence of values is returned.

Union - When the object returned by this method is enumerated, Union enumerates first and second in that order and yields each element that has not already been yielded.

Edit: I've moved Distinct to Preserving order based on this implementation.

    private static IEnumerable<TSource> DistinctIterator<TSource>
      (IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
    {
        Set<TSource> set = new Set<TSource>(comparer);
        foreach (TSource element in source)
            if (set.Add(element)) yield return element;
    }

Actually, I think Distinct preserves original (first found) order - so {1,2,1,3,1,3,4,1,5} would be {1,2,3,4,5}
msdn.microsoft.com/en-us/library/bb348436.aspx The Distinct<(Of <(TSource>)>)(IEnumerable<(Of <(TSource>)>)) method returns an unordered sequence that contains no duplicate values.
Marc: what you say could be true, but it would be a bad idea to rely on that behavior.
@Amy B yes but it doesn't apply to Linq to Objects. In Linq to Sql, distinct() puts the distinct keyword into the generated sql, and ordering from sql is not guaranteed. I'd be interested to see an implementation of distinct for linq to objects that doesn't preserve order and is more efficient that one that does preserve order. For example, you can consume the entire input and put it in a hashset, then yield values by enumerating the hashset (losing order), but that's worse. So yeah, I don't mind defying documentation every now and then :)
Maybe the documentation (for Distinct method) just meant to say "unsorted", not "in unpredictable order". I'd say Distinct belongs to the filtering category above, just like Where.
J
Jon Skeet

Are you actually talking about SQL, or about arrays? To put it another way, are you using LINQ to SQL or LINQ to Objects?

The LINQ to Objects operators don't actually change their original data source - they build sequences which are effectively backed by the data source. The only operations which change the ordering are OrderBy/OrderByDescending/ThenBy/ThenByDescending - and even then, those are stable for equally ordered elements. Of course, many operations will filter out some elements, but the elements which are returned will be in the same order.

If you convert to a different data structure, e.g. with ToLookup or ToDictionary, I don't believe order is preserved at that point - but that's somewhat different anyway. (The order of values mapping to the same key is preserved for lookups though, I believe.)


so because OrderBy is a stable sort, then: seq.OrderBy( _ => _.Key ) will put the elements in to exactly the same order as seq.GroupBy( _ => _.Key ).SelectMany( _ => _ ). Is that correct?
@dmg: No, it won't. Just GroupBy followed by SelectMany will give the results grouped by key, but not in ascending key order... it will give them in the order in which the keys originally occurred.
are you saying that LINQ to SQL does not preserver order?
@symbiont: In many SQL operations there is no well-defined order to start with. Basically I'm trying to only make promises about things I can guarantee - such as LINQ to Objects.
@Paulustrious: In LINQ to Objects, yes. In other providers, it's implementation-specific.
M
Marc Gravell

If you are working on an array, it sounds like you are using LINQ-to-Objects, not SQL; can you confirm? Most LINQ operations don't re-order anything (the output will be in the same order as the input) - so don't apply another sort (OrderBy[Descending]/ThenBy[Descending]).

[edit: as Jon put more clearly; LINQ generally creates a new sequence, leaving the original data alone]

Note that pushing the data into a Dictionary<,> (ToDictionary) will scramble the data, as dictionary does not respect any particular sort order.

But most common things (Select, Where, Skip, Take) should be fine.


If I'm not mistaken, ToDictionary() merely makes no promises about the order, but in practice maintains the input order (until you remove something from it). I'm not saying to rely on this, but 'scrambling' seems inaccurate.
C
Community

I found a great answer in a similar question which references official documentation. To quote it:

For Enumerable methods (LINQ to Objects, which applies to List<T>), you can rely on the order of elements returned by Select, Where, or GroupBy. This is not the case for things that are inherently unordered like ToDictionary or Distinct.

From Enumerable.GroupBy documentation: The IGrouping objects are yielded in an order based on the order of the elements in source that produced the first key of each IGrouping. Elements in a grouping are yielded in the order they appear in source.

This is not necessarily true for IQueryable extension methods (other LINQ providers).

Source: Do LINQ's Enumerable Methods Maintain Relative Order of Elements?


l
leppie

Any 'group by' or 'order by' will possibly change the order.


a
andrew pate

The question here is specifically referring to LINQ-to-Objects.

If your using LINQ-to-SQL instead there is no order there unless you impose one with something like:

mysqlresult.OrderBy(e=>e.SomeColumn)

If you do not do this with LINQ-to-SQL then the order of results can vary between subsequent queries, even of the same data, which could cause an intermittant bug.