c# .net linq semantics iorderedenumerable

c# - ¿Cuándo devolver IOrderedEnumerable?



.net linq (2)

Como señala Thomas, saber que un objeto es un IOrderedEnumerable solo nos dice que se ha ordenado de alguna manera, no que se haya ordenado de una manera que querremos mantener.

También vale la pena señalar que el tipo de retorno influirá en las invalidaciones y la capacidad de compilación, pero no en las comprobaciones de tiempo de ejecución:

private static IOrderedEnumerable<int> ReturnOrdered(){return new int[]{1,2,3}.OrderBy(x => x);} private static IEnumerable<int> ReturnOrderUnknown(){return ReturnOrdered();}//same object, diff return type. private static void UseEnumerable(IEnumerable<int> col){Console.WriteLine("Unordered");} private static void UseEnumerable(IOrderedEnumerable<int> col){Console.WriteLine("Ordered");} private static void ExamineEnumerable(IEnumerable<int> col) { if(col is IOrderedEnumerable<int>) Console.WriteLine("Enumerable is ordered"); else Console.WriteLine("Enumerable is unordered"); } public static void Main(string[] args) { //Demonstrate compile-time loses info from return types //if variable can take either: var orderUnknown = ReturnOrderUnknown(); UseEnumerable(orderUnknown);//"Unordered"; orderUnknown = ReturnOrdered(); UseEnumerable(orderUnknown);//"Unordered" //Demonstate this wasn''t a bug in the overload selection: UseEnumerable(ReturnOrdered());//"Ordered"'' //Demonstrate run-time will see "deeper" than the return type anyway: ExamineEnumerable(ReturnOrderUnknown());//Enumerable is ordered. }

Debido a esto, si tiene un caso en el que se pueda devolver a la persona que llama IEnumerable<T> o IOrderedEnumerable<T> , la variable se escribirá como IEnumerable<T> y se IEnumerable<T> la información del tipo de retorno. Mientras tanto, independientemente del tipo de retorno, el llamante podrá determinar si el tipo es realmente IOrderedEnumerable<T> .

De cualquier manera, el tipo de retorno realmente no importaba.

Las compensaciones con los tipos de retorno son entre la utilidad para la persona que llama y la flexibilidad para la persona que llama.

Considere un método que actualmente termina con return currentResults.ToList() . Los siguientes tipos de devolución son posibles:

  1. List<T>
  2. IList<T>
  3. ICollection<T>
  4. IEnumerable<T>
  5. IList
  6. ICollection
  7. IEnumerable
  8. object

Vamos a excluir el objeto y los tipos no genéricos en este momento, ya que es poco probable que sean útiles (en los casos en que serían útiles, probablemente no sean una decisión fácil de usar). Esto deja:

  1. List<T>
  2. IList<T>
  3. ICollection<T>
  4. IEnumerable<T>

Cuanto más arriba en la lista vamos, más conveniencia le damos a la persona que llama para que haga uso de la funcionalidad expuesta por ese tipo, que no está expuesta por el tipo a continuación. Cuanto más abajo de la lista vayamos, más flexibilidad le damos a la persona que llama para cambiar la implementación en el futuro. Por lo tanto, idealmente, queremos ir tan arriba en la lista como tenga sentido en el contexto del propósito del método (para exponer la funcionalidad útil a la persona que llama, y ​​reducir los casos en que se crean nuevas colecciones para ofrecer la funcionalidad que ya ofrecíamos), pero no más. (para permitir futuros cambios).

Por lo tanto, volvamos a nuestro caso en el que tenemos un IOrderedEnumerable<TElement> que podemos devolver como un IOrderedEnumerable<TElement> o IEnumerable<T> (o IEnumerable u object ).

La pregunta es, ¿el hecho de que este es un IOrderedEnumerable relacionado con el propósito del método, o es simplemente un artefacto de implementación?

Si tuviéramos un método ReturnProducts que sucedió a pedido por precio como parte de la implementación de la eliminación de casos en los que el mismo producto se ofreció dos veces a precios diferentes, entonces debería devolver el IEnumerable<Product> , porque a las personas que llaman no les importa que se ordenen. Y, desde luego, no debería depender de ello.

Si tuviéramos un método ReturnProductsOrderedByPrice en el que el pedido formara parte de su propósito, entonces deberíamos devolver IOrderedEnumerable<Product> , ya que esto se relaciona más estrechamente con su propósito, y podemos esperar razonablemente que llame a CreateOrderedEnumerable , ThenBy o ThenByDescending de él (las únicas cosas esto realmente ofrece) y no se ha roto por un cambio posterior en la implementación.

Edit: me perdí la segunda parte de esto.

En el caso de que un repositorio envuelva un procedimiento almacenado con una cláusula ORDER BY. ¿Debería el repositorio devolver IOrderedEnumerable? ¿Y cómo se lograría eso?

Es una buena idea cuando es posible (o quizás IOrderedQueryable<T> ). Sin embargo, no es simple.

Primero, debe asegurarse de que nada posterior al ORDER BY PODRÍA haber deshecho el pedido, esto puede no ser trivial.

En segundo lugar, no debe deshacer este pedido en una llamada a CreateOrderedEnumerable<TKey>() .

Por ejemplo, si los elementos con los campos A , B , C y D se devuelven de algo que usó ORDER BY A DESCENDING, B dio como resultado la devolución de un tipo llamado MyOrderedEnumerable<El> que implementa IOrderedEnumerable<El> . Luego, se debe almacenar el hecho de que A y B son los campos que se ordenaron. Una llamada a CreateOrderedEnumerable(e => eD, Comparer<int>.Default, false) (que también es a lo que ThenBy y ThenByDescending call) debe ThenByDescending grupos de elementos que se comparan por igual para A y B , por las mismas reglas para las que fueron devueltos por la base de datos (las intercalaciones coincidentes entre las bases de datos y .NET pueden ser difíciles), y solo dentro de esos grupos deben ordenarse según cmp.Compare(e0.D, e1.D) .

Si pudiera hacer eso, podría ser muy útil, y sería totalmente apropiado que el tipo de devolución fuera IOrderedEnumerable si las cláusulas ORDER BY estuvieran presentes en todas las consultas utilizadas por todas las llamadas.

De lo contrario, IOrderedEnumerable sería una mentira, ya que no podría cumplir con el contrato que ofrece, y sería menos que inútil.

¿ IOrderedEnumerable debe utilizar IOrderedEnumerable como tipo de retorno puramente para el valor semántico?

Por ejemplo, al consumir un modelo en la capa de presentación, ¿cómo podemos saber si la colección requiere ordenamiento o ya está ordenado?

En el caso de que un repositorio envuelva un procedimiento almacenado con una cláusula ORDER BY . ¿Debería el repositorio devolver IOrderedEnumerable ? ¿Y cómo se lograría eso?


No creo que sea una buena idea:

¿Se debe utilizar IOrderedEnumerable como tipo de retorno puramente para el valor semántico?

Por ejemplo, al consumir un modelo en la capa de presentación, ¿cómo podemos saber si la colección requiere ordenamiento o ya está ordenado?

¿Cuál es el punto de saber que una secuencia está ordenada si no sabe con qué clave está ordenada? El punto de la interfaz IOrderedEnumerable es poder agregar un criterio de clasificación secundario, que no tiene mucho sentido si no sabes cuál es el criterio principal.

En el caso de que un repositorio envuelva un procedimiento almacenado con una cláusula ORDER BY. ¿Debería el repositorio devolver IOrderedEnumerable? ¿Y cómo se lograría eso?

Esto no tiene sentido. Como ya dije, IOrderedEnumerable se usa para agregar un criterio de clasificación secundario, pero cuando los datos son devueltos por el procedimiento almacenado, ya está ordenado y es demasiado tarde para agregar un criterio de clasificación secundario. Todo lo que puede hacer es reorganizarlo por completo, por lo que llamar a ThenBy sobre el resultado no tendría el efecto esperado.