Preservar el orden con LINQ

361

Uso las instrucciones de LINQ to Objects en una matriz ordenada. ¿Qué operaciones no debo hacer para asegurarme de que no se cambie el orden de la matriz?

Matthieu Durut
fuente

Respuestas:

645

Examiné los métodos de System.Linq.Enumerable , descartando cualquiera que arrojara resultados no IEnumerable. Revisé los comentarios de cada uno para determinar cómo el orden del resultado diferiría del orden de la fuente.

Preserva el orden absolutamente. Puede asignar un elemento fuente por índice a un elemento resultado

  • AsEnumerable
  • Emitir
  • Concat
  • Seleccione
  • ToArray
  • Listar

Preserva el orden. Los elementos se filtran o agregan, pero no se reordenan.

  • Distinto
  • Excepto
  • Intersecarse
  • OfType
  • Anteponer (nuevo en .net 4.7.1)
  • Omitir
  • SkipWhile
  • Tomar
  • TakeWhile
  • Dónde
  • Zip (nuevo en .net 4)

Destruye el orden: no sabemos en qué orden esperar resultados.

  • ToDictionary
  • Para buscar

Redefine el orden explícitamente: utilícelos para cambiar el orden del resultado

  • OrderBy
  • OrderByDescending
  • Contrarrestar
  • Entonces por
  • EntoncesPor Descendente

Redefine el orden de acuerdo con algunas reglas.

  • GroupBy: los objetos IGrouping se obtienen en un orden basado en el orden de los elementos en origen que produjeron la primera clave de cada IGrouping. Los elementos de una agrupación se obtienen en el orden en que aparecen en la fuente.
  • GroupJoin: GroupJoin conserva el orden de los elementos externos y, para cada elemento externo, el orden de los elementos coincidentes de los internos.
  • Unir: conserva el orden de los elementos externos y, para cada uno de estos elementos, el orden de los elementos coincidentes internos.
  • SelectMany: para cada elemento de origen, se invoca el selector y se devuelve una secuencia de valores.
  • Unión: cuando se enumera el objeto devuelto por este método, Unión enumera primero y segundo en ese orden y produce cada elemento que aún no se ha producido.

Editar: He movido Distinct a Preserve order basado en esta implementación .

    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
fuente
2
En realidad, creo que Distinct conserva el orden original (primero encontrado), por lo que {1,2,1,3,1,3,4,1,5} sería {1,2,3,4,5}
Marc Gravell
10
msdn.microsoft.com/en-us/library/bb348436.aspx El método distintivo <(Of <(TSource>)>) (IEnumerable <(Of <(TSource>)>)) devuelve una secuencia desordenada que no contiene valores duplicados .
Amy B
12
Marc: lo que dices podría ser cierto, pero sería una mala idea confiar en ese comportamiento.
Amy B
44
@Amy B sí, pero no se aplica a Linq a objetos. En Linq to Sql, distinct () coloca la palabra clave distinta en el sql generado, y no se garantiza el pedido desde sql. Me interesaría ver una implementación distinta de linq para objetos que no preserven el orden y que sea más eficiente que la que preserve el orden. Por ejemplo, puede consumir toda la entrada y ponerla en un hashset, luego generar valores enumerando el hashset (orden de pérdida), pero eso es peor. Así que sí, no me importa desafiar la documentación de vez en cuando :)
dan
44
Tal vez la documentación (para el Distinctmétodo) solo significaba "sin clasificar", no "en orden impredecible". Yo diría que Distinctpertenece a la categoría de filtrado anterior, al igual que Where.
Jeppe Stig Nielsen
34

¿Estás hablando realmente de SQL o de matrices? Para decirlo de otra manera, ¿estás usando LINQ to SQL o LINQ to Objects?

Los operadores de LINQ to Objects en realidad no cambian su fuente de datos original: construyen secuencias que están respaldadas efectivamente por la fuente de datos. Las únicas operaciones que cambian el orden son OrderBy / OrderByDescending / ThenBy / ThenByDescending, y aun así, son estables para elementos igualmente ordenados. Por supuesto, muchas operaciones filtrarán algunos elementos, pero los elementos que se devuelven estarán en el mismo orden.

Si convierte a una estructura de datos diferente, por ejemplo, con ToLookup o ToDictionary, no creo que se mantenga el orden en ese punto, pero de todos modos eso es algo diferente. (Creo que el orden de asignación de valores a la misma clave se conserva para las búsquedas).

Jon Skeet
fuente
entonces, porque OrderBy es una ordenación estable, entonces: seq.OrderBy (_ => _.Key) colocará los elementos exactamente en el mismo orden que seq.GroupBy (_ => _.Key) .SelectMany (_ => _ ) ¿Es eso correcto?
dmg
1
@dmg: No, no lo hará. Solo GroupByseguido de SelectManydará los resultados agrupados por clave, pero no en orden ascendente de la clave ... los dará en el orden en que ocurrieron las claves originalmente.
Jon Skeet
¿Estás diciendo que LINQ to SQL no conserva el orden?
simbionte
@symbiont: En muchas operaciones de SQL no es el fin bien definido para empezar. Básicamente, estoy tratando de hacer promesas sobre cosas que puedo garantizar, como LINQ to Objects.
Jon Skeet
@ JonSkeet Si uso OrderBy, ¿garantiza que 'n' objetos con la misma clave conservarán su secuencia original, excepto que están todos juntos? es decir: list<x> {a b c d e f g}si c, d, e tienen la misma clave, la secuencia resultante contendrá c, d, e una al lado de la otra Y en el orden c, d, e. Parece que no puedo encontrar una respuesta categórica basada en MS.
Paulustrious
7

Si está trabajando en una matriz, parece que está utilizando LINQ-to-Objects, no SQL; ¿puedes confirmar? La mayoría de las operaciones de LINQ no reordenan nada (la salida estará en el mismo orden que la entrada), así que no aplique otro tipo (OrderBy [Descending] / ThenBy [Descending]).

[editar: como Jon puso más claramente; LINQ generalmente crea una nueva secuencia, dejando solo los datos originales]

Tenga en cuenta que Dictionary<,>insertar los datos en un (ToDictionary) codificará los datos, ya que el diccionario no respeta ningún orden de clasificación en particular.

Pero las cosas más comunes (Seleccionar, Dónde, Saltar, Tomar) deberían estar bien.

Marc Gravell
fuente
Si no me equivoco, ToDictionary()simplemente no hace promesas sobre el orden, sino que en la práctica mantiene el orden de entrada (hasta que elimine algo de él). No estoy diciendo que confíe en esto, pero 'revolver' parece inexacto.
Timo
4

Encontré una gran respuesta en una pregunta similar que hace referencia a la documentación oficial. Para citarlo:

Para Enumerablemétodos (LINQ a objetos, que se aplica a List<T>), se puede confiar en el orden de los elementos devueltos por Select, Whereo GroupBy. Este no es el caso para cosas que son inherentemente desordenadas como ToDictionaryo Distinct.

De la documentación de Enumerable.GroupBy :

Los IGrouping<TKey, TElement>objetos se entregan en un orden basado en el orden de los elementos en origen que produjeron la primera clave de cada uno IGrouping<TKey, TElement>. Los elementos de una agrupación se obtienen en el orden en que aparecen source.

Esto no es necesariamente cierto para los IQueryablemétodos de extensión (otros proveedores de LINQ).

Fuente: ¿Los métodos enumerables de LINQ mantienen el orden relativo de los elementos?

Curtis Yallop
fuente
2

Cualquier 'agrupar por' u 'ordenar por' posiblemente cambiará el orden.

leppie
fuente
0

La pregunta aquí se refiere específicamente a LINQ-to-Objects.

Si usa LINQ-to-SQL en su lugar, no hay orden allí a menos que imponga uno con algo como:

mysqlresult.OrderBy(e=>e.SomeColumn)

Si no hace esto con LINQ-to-SQL, el orden de los resultados puede variar entre consultas posteriores, incluso de los mismos datos, lo que podría causar un error intermitente.

Andrew Paté
fuente