Preservare l'ordine con LINQ


Risposte:


645

Ho esaminato i metodi di System.Linq.Enumerable , scartando quelli che restituivano risultati non IEnumerable. Ho controllato le osservazioni di ciascuno per determinare in che modo l'ordine del risultato differirebbe dall'ordine della fonte.

Conserva l'ordine assolutamente. È possibile mappare un elemento sorgente per indice a un elemento risultato

  • AsEnumerable
  • Fusioni
  • concat
  • Selezionare
  • ToArray
  • Elencare

Conserva l'ordine. Gli elementi vengono filtrati o aggiunti, ma non riordinati.

  • distinto
  • tranne
  • Intersect
  • OfType
  • Prepend (nuovo in .net 4.7.1)
  • Salta
  • SkipWhile
  • Prendere
  • TakeWhile
  • Dove
  • Zip (nuovo in .net 4)

Distrugge l'ordine: non sappiamo in quale ordine aspettarsi i risultati.

  • ToDictionary
  • ToLookup

Ridefinisce l'ordine esplicitamente: utilizzare questi per modificare l'ordine del risultato

  • Ordinato da
  • OrderByDescending
  • Inversione
  • ThenBy
  • ThenByDescending

Ridefinisce l'ordine secondo alcune regole.

  • GroupBy: gli oggetti IGrouping vengono generati in un ordine basato sull'ordine degli elementi nell'origine che ha prodotto la prima chiave di ciascun IGrouping. Gli elementi in un raggruppamento sono prodotti nell'ordine in cui appaiono nella fonte.
  • GroupJoin - GroupJoin conserva l'ordine degli elementi dell'esterno e, per ogni elemento dell'esterno, l'ordine degli elementi corrispondenti dall'interno.
  • Unisci: conserva l'ordine degli elementi esterni e, per ciascuno di questi elementi, l'ordine degli elementi corrispondenti interni.
  • SelectMany: per ogni elemento di origine, viene richiamato il selettore e viene restituita una sequenza di valori.
  • Union: quando viene elencato l'oggetto restituito con questo metodo, Union enumera il primo e il secondo in quell'ordine e produce ogni elemento che non è già stato prodotto.

Modifica: ho spostato Distinto in Ordine di conservazione in base a questa implementazione .

    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;
    }

2
In realtà, penso che Distinct conservi l'ordine originale (il primo trovato), quindi {1,2,1,3,1,3,4,1,5} sarebbe {1,2,3,4,5}
Marc Gravell

10
msdn.microsoft.com/en-us/library/bb348436.aspx Il metodo Distinct <(Of <(TSource>)>) (IEnumerable <(Of <(TSource>)>)) restituisce una sequenza non ordinata che non contiene valori duplicati .
Amy B,

12
Marc: quello che dici potrebbe essere vero, ma sarebbe una cattiva idea fare affidamento su quel comportamento.
Amy B,

4
@Amy B sì, ma non si applica a Linq to Objects. In Linq a Sql, distingue () inserisce la parola chiave distinta nel sql generato e l'ordine da sql non è garantito. Sarei interessato a vedere un'implementazione distinta per linq agli oggetti che non preserva l'ordine ed è più efficiente di quella che preserva l'ordine. Ad esempio, puoi consumare l'intero input e inserirlo in un hashset, quindi produrre valori enumerando l'hashset (ordine perdente), ma è peggio. Quindi sì, non mi dispiace sfidare la documentazione ogni tanto :)
dan

4
Forse la documentazione (per Distinctmetodo) significava solo "non ordinata", non "in ordine imprevedibile". Direi che Distinctappartiene alla categoria di filtro sopra, proprio come Where.
Jeppe Stig Nielsen,

34

In realtà stai parlando di SQL o di array? Per dirla in altro modo, stai usando LINQ to SQL o LINQ to Objects?

Gli operatori LINQ to Objects in realtà non modificano l'origine dati originale, ma creano sequenze che sono effettivamente supportate dall'origine dati. Le uniche operazioni che modificano l'ordinamento sono OrderBy / OrderByDescending / ThenBy / ThenByDescending - e anche in questo caso, sono stabili per gli elementi equamente ordinati. Naturalmente, molte operazioni filtrano alcuni elementi, ma gli elementi che vengono restituiti saranno nello stesso ordine.

Se ti converti in una diversa struttura di dati, ad esempio con ToLookup o ToDictionary, non credo che l'ordine venga conservato a quel punto, ma è comunque leggermente diverso. (Credo che l'ordine dei valori associati alla stessa chiave sia conservato per le ricerche).


quindi poiché OrderBy è un ordinamento stabile, quindi: seq.OrderBy (_ => _.Key) inserirà gli elementi esattamente nello stesso ordine di seq.GroupBy (_ => _.Key) .SelectMany (_ => _ ). È corretto?
dmg

1
@dmg: No, non lo farà. Appena GroupByseguito da SelectManydarà i risultati raggruppati per chiave, ma non in ordine crescente di chiave ... li darà nell'ordine in cui le chiavi si sono originariamente verificate.
Jon Skeet,

stai dicendo che LINQ to SQL non ordina l'ordine?
simbionion

@symbiont: in molte operazioni SQL non esiste un ordine ben definito con cui iniziare. Fondamentalmente sto cercando di fare solo promesse su cose che posso garantire - come LINQ to Objects.
Jon Skeet,

@JonSkeet Se uso OrderBy, garantisce che 'n' oggetti con la stessa chiave conserveranno la loro sequenza originale tranne che sono tutti insieme. vale a dire: list<x> {a b c d e f g}se c, d, e hanno tutti la stessa chiave, la sequenza risultante conterrà c, d, e uno accanto all'altro E nell'ordine c, d, e. Non riesco a trovare una risposta categorica basata sulla SM.
Pauroso

7

Se stai lavorando su un array, sembra che tu stia usando LINQ-to-Objects, non SQL; Puoi confermare? La maggior parte delle operazioni LINQ non riordina nulla (l'output sarà nello stesso ordine dell'input) - quindi non applicare un altro ordinamento (OrderBy [Descending] / ThenBy [Descending]).

[modifica: come Jon ha messo più chiaramente; LINQ generalmente crea una nuova sequenza, lasciando da soli i dati originali]

Notare che spingendo i dati in un Dictionary<,>(ToDictionary) si mescolano i dati, poiché il dizionario non rispetta alcun particolare ordinamento.

Ma le cose più comuni (Seleziona, Dove, Salta, Prendi) dovrebbero andare bene.


Se non sbaglio, non faccio ToDictionary()semplicemente promesse sull'ordine, ma in pratica mantiene l'ordine di input (fino a quando non rimuovi qualcosa da esso). Non sto dicendo di fare affidamento su questo, ma "rimescolare" sembra impreciso.
Timo,

4

Ho trovato un'ottima risposta in una domanda simile che fa riferimento alla documentazione ufficiale. Per citarlo:

Per Enumerablei metodi (LINQ to Objects, che si applica a List<T>), si può contare su l'ordine degli elementi restituiti da Select, Whereo GroupBy. Questo non è il caso di cose intrinsecamente non ordinate come ToDictionaryo Distinct.

Da Enumerable.GroupBy documentazione di :

Gli IGrouping<TKey, TElement>oggetti vengono generati in un ordine basato sull'ordine degli elementi in sorgente che hanno prodotto la prima chiave di ciascuno IGrouping<TKey, TElement>. Gli elementi in un raggruppamento vengono prodotti nell'ordine in cui appaiono source.

Questo non è necessariamente vero per i IQueryablemetodi di estensione (altri provider LINQ).

Fonte: i metodi enumerabili di LINQ mantengono l'ordine relativo degli elementi?


2

Qualsiasi "raggruppa per" o "ordina per" modificherà eventualmente l'ordine.


0

La domanda qui si riferisce specificamente a LINQ-to-Objects.

Se si utilizza LINQ-to-SQL invece non c'è alcun ordine a meno che non si imponga uno con qualcosa di simile:

mysqlresult.OrderBy(e=>e.SomeColumn)

Se non lo si fa con LINQ-to-SQL, l'ordine dei risultati può variare tra le query successive, anche degli stessi dati, che potrebbero causare un bug intermittente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.