412

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?

Machavity
  • 30,841
  • 27
  • 92
  • 100
Matthieu Durut
  • 4,838
  • 3
  • 20
  • 16

7 Answers7

739

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;
    }
Amy B
  • 108,202
  • 21
  • 135
  • 185
  • 3
    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} – Marc Gravell Oct 15 '08 at 14:33
  • 10
    http://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. – Amy B Oct 15 '08 at 14:47
  • 13
    Marc: what you say could be true, but it would be a bad idea to rely on that behavior. – Amy B Oct 15 '08 at 14:52
  • 1
    @Amy B I think it would be unreasonable to implement Distinct (for linq to objects) in a way that doesn't preserve order, since that would perform worse than if it did preserve order. – dan Jul 14 '11 at 06:34
  • 1
    @dan *shrug* if you want to defy the documentation, that's up to you. – Amy B Aug 18 '11 at 01:16
  • 5
    @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 :) – dan Aug 19 '11 at 03:39
  • @Dan "Found First" destroys order. In that list the 1 is before and after the 4. By making it a distinct list, that ordering is gone. – Rangoric Aug 31 '11 at 15:51
  • 1
    @Rangoric - depends on your definition of preserves order. If we go with the FoundFirst for Distinct - one could aruge that elements are filtered and not reordered. The only reason I have for not choosing that option, is the documentation says "unordered". – Amy B Sep 02 '11 at 17:16
  • @Amy and that's why. Found First isn't the same as Distinct. If there comes a time when someone has a way to do distinct without preserving order that is faster, then by all means, I don't want to hold them back because of an assumption. – Rangoric Sep 02 '11 at 22:19
  • 4
    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`. – Jeppe Stig Nielsen Sep 20 '12 at 16:17
  • I'm not sure about select many, as there is no other constructions besides two foreaches. What side effect are you talking about? – Johnny_D Mar 10 '15 at 12:22
  • 1
    @Johnny_D I've considered my use of the term "side effect", and decided to edit since it has a meaning in our field. Also, when I'm talking about "preserving order", I'm talking from the perspective of Order Isomorphism. Distinct and SelectMany are not order isomorphic. – Amy B Mar 18 '15 at 12:22
  • Aren't Except, Intersect Union etc set operations? I wouldnt trust them to return in order. Is it documented? – nawfal Jun 29 '15 at 19:37
  • 1
    @nawfal Intersect ( https://msdn.microsoft.com/en-us/library/vstudio/bb460136(v=vs.100).aspx ): "Finally, the marked elements are yielded in the order in which they were collected." Except doesn't document this, but its implementation is likely to be very similar. Union ( https://msdn.microsoft.com/en-us/library/vstudio/bb341731(v=vs.100).aspx ) : quote is already in the answer. While these operations could be described as set operations, they operate on IEnumerable and it makes sense for their implementations to have a documented order based on the order of the source IEnumerable. – Amy B Jun 29 '15 at 23:33
  • @nawfal also note: this question and answer are about LinqToObjects (the methods of System.Linq.Enumerable). If you are using Linq to anything else (System.Linq.Queryable) - none of this applies. – Amy B Jun 29 '15 at 23:38
  • @AmyB For `ILookup`: *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.*, taken from the GroupBy documentation. Since `ILookup` has `IGrouping`s, it's guaranteed to preserve order, right? – mbomb007 Jul 08 '15 at 16:51
  • https://github.com/dotnet/corefx/blob/master/src/System.Linq.Parallel/src/System/Linq/Parallel/Utils/Lookup.cs ToLookup returns a Lookup. As you can see in the code, Lookup wraps a Dictionary. Because of this, Lookup has the same inconsistent ordering as Dictionary. – Amy B Nov 17 '15 at 16:28
  • [Lookup](https://msdn.microsoft.com/library/bb460184%28v=vs.100%29.aspx) indeed wraps a dictionary but a special one: it maintains links between the elements to preserve the ordering of group keys, because it has to fulfil the documented ordering of `GroupBy`. Lookup is [the underlying data structure of both](https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Enumerable.cs) `GroupBy` and `ToLookup`. Therefore `ToLookup` preserves ordering just as well as `Distinct` does: neither is documented, both works (for ≥ 6 years now). – robert4 Nov 19 '15 at 11:24
  • But Lookup is backed by a plain Dictionary: _dict = new Dictionary>(_comparer); Does this mean that Dictionary preserves order? – Amy B Nov 19 '15 at 15:39
  • @Amy B The plain Dictionary preserves ordering (albeit undocumented) as long as no elements are removed, but it is *not* used in [Lookup](https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Enumerable.cs#l3230). Its internal [GetGrouping()](https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Enumerable.cs#l3358) method contains a hash table implementation, with the aforementioned .next pointers, to be used in GetEnumerator() (line 3288). – robert4 Nov 23 '15 at 08:33
  • 1
    @robert4 it looks like `ToLookup` changes the keys order, but the elements preserve the order of the source enumerable. – HuBeZa Feb 02 '16 at 12:45
  • @HuBeZa can you show an example on [dotnetfiddle.net](https://dotnetfiddle.net/)? It sounds me impossible. Are you speaking about Linq.Parallel or the normal(serial) one? – robert4 Feb 02 '16 at 18:43
  • 1
    @robert4 take a look at [`ToLookup`](http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,b8412da996013291) which calls [`Lookup.Create`](http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,86a042202a06bb2c). It runs serially on the source enumerable and calls [`Grouping.Add`](http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,234084c73b27f03d) that encapsulate a simple array buffer. – HuBeZa Feb 03 '16 at 07:42
  • @HuBeZa This proves that the order of elements is preserved within the groups, which I didn't question. I contradicted with the first half of your claim: _“ToLookup changes the keys order”_. `Lookup.Create` calls [lookup.GetGrouping()](http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,57b0aa9f1c572776) for every item of the source, which creates groups in a manner that preserves the ordering of keys, as I detailed above. – robert4 Feb 03 '16 at 13:00
  • You didn't mention `Aggregate`. `Aggregate` follows the order because the concept of `Aggregate` requires that it follows the order. Here's the [Reference Source](https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,ac261011ebd4a616) to verify this. I realize that since it's kind of a given, it might not be important enough to mention, but it's not mentioned anywhere on this Q&A. – Zach Mierzejewski Mar 17 '17 at 16:05
  • 2
    @ZachMierzejewski Aggregate was excluded because it does not return an IEnumerable. With no enumerable result, the order of the result does not exist and cannot be defined as preserved or not. It is important to know that Aggregate processes the input in order, same as all the methods which have an IEnumerable source. – Amy B Mar 17 '17 at 16:57
  • I think it's funny that no one ever asked me to move Union to Preserving Order... it has the same Found First behavior as Distinct. – Amy B Jun 08 '18 at 18:45
  • So why doesn't, eg, [`Skip`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.take?view=netframework-4.7.2) (and other things on the order preserved list) return an `IOrderedEnumerable`? – ruffin Sep 07 '18 at 21:27
  • @ruffin It might be helpful to think about what `IOrderedEnumerable` is for. Without this interface, we could not call `ThenBy`. Does it make sense to call `customers.Skip(5).ThenBy(c => c.Name)` ? `Skip` does not define an ordering - its implementation doesn't change the order. – Amy B Sep 08 '18 at 07:47
  • Thanks for the reply! The flipside of this, however, is that `IEnumerable` **doesn't guarantee** order, and I wouldn't've assumed I could count on something returning `IEnumerable` "*doesn't change the order*". It seems smelly to say that a function that returns an unordered type guarantees that unordered type's order is preserved. Wait, what? ;^) If `customers` has an internalized idea of how it's ordered (ie, the formula by which it's ordered, which I admit I don't think is the case), then your `Skip` usage would, in fact, make sense. `orderedByCountyCustomers.Skip(5).ThenBy(c => c.Name)` – ruffin Sep 10 '18 at 15:14
  • Interesting continuation of your point [in this answer from Eric Lippert & discussion with Skeet](https://stackoverflow.com/a/37973574/1028230). Still seems weird to pretend an `IEnumerable` has an order to be preserved when it doesn't guarantee order (ie, is implicitly unordered)... I think it's probably better for me to think that an IEnumerable *can* be ordered, but doesn't, *by itself*, **guarantee** order. `IOrderedEnumerable` probably isn't the best name for what it's doing either.. `IInTheProcessOfBeingOrderedEnumerable`? /shrug Thanks again. – ruffin Sep 10 '18 at 15:33
  • @ruffin most `IEnumerable` have an order (for example, array index order). When they do, the order is preserved by the operations. When they don't, there is no order to preserve. All `IOrderedEnumerable` have a defined order. That defined order is respected and modified by calls to `ThenBy`. – Amy B Sep 10 '18 at 16:17
  • 1
    I think you were correct to go by the documented behavior of `Distinct` and not the implementation. Nothing says .Net has to run on a Von Neumann architecture - on a version for a Connection Machine, perhaps not preserving order would be a better implementation. – NetMage Nov 29 '18 at 00:26
  • Great list! But I don't see anything in the `Select` remarks or anywhere else on the page that states the order is guaranteed to be preserved. – xr280xr Dec 15 '21 at 22:28
  • Hi @xr280xr , the indexed overload in the Select documentation mentions processing and returning the items in order. https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select?view=net-6.0 – Amy B Dec 16 '21 at 14:49
  • Hey @AmyB The only mention of the word "order" on that page says that selector overload can provide the item's index in the "_source sequence_". But that doesn't indicate what the `Select` function returns. Can you quote part of where you're seeing a mention of returning the items in order? – xr280xr Dec 16 '21 at 18:41
  • It seems that the old debate about ToLookup was never resolved, but Microsoft's own documentation for multiple versions of ToLookup (e.g. Framework 4.5, .Net 6, etc.) all say for ToLookup() overloads that "The values within each group are in the same order as in source." – C Perkins Jul 19 '23 at 17:23
40

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.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 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 Feb 01 '16 at 22:11
  • 1
    @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. – Jon Skeet Feb 01 '16 at 22:54
  • are you saying that LINQ to SQL does not preserver order? – symbiont Aug 19 '16 at 08:39
  • @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. – Jon Skeet Aug 19 '16 at 08:48
  • @JonSkeet If I use OrderBy does it guarantee that 'n' objects with the same key will preserve their original sequence except that they are all together. ie.:in `list {a b c d e f g}` if c,d,e all have the same key then the resulting sequence will contain c,d,e next to each other AND in the order c,d,e. I can't seem to find a categorical MS-based answer. – Paulustrious Oct 10 '17 at 18:28
  • 1
    @Paulustrious: In LINQ to Objects, yes. In other providers, it's implementation-specific. – Jon Skeet Oct 10 '17 at 18:29
  • @JonSkeet You're good. You answered while I was still editing my question – Paulustrious Oct 10 '17 at 18:35
  • @Paulustrious: From https://msdn.microsoft.com/en-us/library/bb534966(v=vs.110).aspx: "This method performs a stable sort; that is, if the keys of two elements are equal, the order of the elements is preserved. In contrast, an unstable sort does not preserve the order of elements that have the same key." – Jon Skeet Oct 10 '17 at 18:36
  • Regarding the statement about ToLookup not preserving order, Microsoft's own documentation for multiple versions of ToLookup (e.g. Framework 4.5, .Net 6, etc.) all say for ToLookup() overloads that "The values within each group are in the same order as in source." The keys are not necessary stored in the same order, but enumeration or indexing into the Lookup object does preserve the order. – C Perkins Jul 19 '23 at 17:25
  • @CPerkins: Hence "The order of values mapping to the same key is preserved for lookups though, I believe." – Jon Skeet Jul 19 '23 at 20:18
8

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 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. – Timo Oct 17 '17 at 13:42
5

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<TKey, TElement> objects are yielded in an order based on the order of the elements in source that produced the first key of each IGrouping<TKey, TElement>. 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?

Community
  • 1
  • 1
Curtis Yallop
  • 6,696
  • 3
  • 46
  • 36
2

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

leppie
  • 115,091
  • 17
  • 196
  • 297
0

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.

andrew pate
  • 3,833
  • 36
  • 28
0

For me the issue was determining the default sort order which turned out to be by 2 columns as shown below. After many iterations I was able to find the default sort order and redo it in my LINQ query. To remove duplicates a simple foreach is used to create a new list of strings without duplicates.

//original sorting order lost
var inv2 = db.Inventories
.GroupBy(l => l.VendorFullSKU)
.Select(cl => new Inventory2
{
    VariantID = cl.FirstOrDefault() == null ? 0 : cl.FirstOrDefault().VariantID,
    Quan = cl.Sum(c => c.Quan), 
    Color = cl.FirstOrDefault() == null ? "" : cl.FirstOrDefault().Color    
});


//original sorting order restored
var bl = (from pv in db.ProductVariants
join inv in inv2 on pv.VariantID equals inv.VariantID
orderby inv.VariantID, inv.Color //sort
select inv.Color
).ToList();


//remove duplicates while preserving original sort order
var colorsDistinct = new List<string>();
foreach (var item in bl)
{
    if (!colorsDistinct.Contains(item))
        colorsDistinct.Add(item);
}
estinamir
  • 435
  • 5
  • 11