Подтвердить что ты не робот

Когда возвращать IOrderedEnumerable?

Должен ли IOrderedEnumerable использоваться как возвращаемый тип только для семантического значения?

Например, когда вы потребляете модель на уровне презентации, как мы можем узнать, требует ли сбор заказывать или уже заказывается?

Как насчет того, что репозиторий обертывает хранимую процедуру с помощью предложения ORDER BY. Должно ли хранилище вернуться IOrderedEnumerable? И как это будет достигнуто?

4b9b3361

Ответ 1

Я не думаю, что это была бы хорошая идея:

Должен ли IOrderedEnumerable использоваться как возвращаемый тип только для семантического значения?

Например, когда вы потребляете модель на уровне презентации, как мы можем узнать, требует ли сбор заказывать или уже заказывается?

В чем смысл знать, что последовательность упорядочена, если вы не знаете, по какому ключу она заказана? Точка интерфейса IOrderedEnumerable должна иметь возможность добавлять вторичные критерии сортировки, что не имеет большого смысла, если вы не знаете, что является основным критерием.

Как насчет того, что репозиторий обертывает хранимую процедуру с предложением ORDER BY. Должен ли репозиторий возвращать IOrderedEnumerable? И как это будет достигнуто?

Это не имеет смысла. Как я уже сказал, IOrderedEnumerable используется для добавления вторичных критериев сортировки, но когда данные возвращаются хранимой процедурой, они уже отсортированы и слишком поздно добавлять вторичные критерии сортировки. Все, что вы можете сделать, это пересортировать его полностью, поэтому вызов ThenBy в результате не будет иметь ожидаемого эффекта.

Ответ 2

Как указывает Томас, знание того, что объект является IOrderedEnumerable, говорит нам только о том, что он был каким-то образом заказан, а не потому, что он был заказан таким образом, который мы хотим поддерживать.

Также стоит отметить, что тип возврата будет влиять на переопределения и возможность компиляции, но не проверки времени выполнения:

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

Из-за этого, если у вас есть случай, когда в зависимости от обстоятельств может быть возвращено IEnumerable<T> или IOrderedEnumerable<T> в зависимости от обстоятельств, переменная будет набираться как IEnumerable<T>, а информация из возвращаемого типа будет потеряна. Между тем, независимо от типа возврата, вызывающий может определить, действительно ли тип IOrderedEnumerable<T>.

В любом случае тип возврата не имеет особого значения.

Компромисс с типами возврата между полезностью для вызывающего и гибкостью для вызываемого абонента.

Рассмотрим метод, который в настоящее время заканчивается на return currentResults.ToList(). Возможны следующие типы возврата:

  • List<T>
  • IList<T>
  • ICollection<T>
  • IEnumerable<T>
  • IList
  • ICollection
  • IEnumerable
  • object

Позвольте исключить объект и не общие типы прямо сейчас, поскольку вряд ли они будут полезны (в тех случаях, когда они были бы полезны, они, вероятно, не требуют разумных решений). Это оставляет:

  • List<T>
  • IList<T>
  • ICollection<T>
  • IEnumerable<T>

Чем выше список, тем выше удобство, мы предоставляем вызывающему абоненту использовать функциональность, открытую этим типом, которая не отображается по типу ниже. Чем ниже список, тем выше гибкость, которую мы предоставляем вызываемому лицу для изменения реализации в будущем. В идеале, поэтому мы хотим сделать так же высоко в списке, как это имеет смысл в контексте цели метода (чтобы показать полезную функциональность вызывающей стороне и сократить случаи создания новых коллекций, чтобы предлагать функциональные возможности, которые мы уже предлагали), но не выше (чтобы разрешить будущие изменения).

Итак, вернемся к нашему случаю, когда у нас есть IOrderedEnumerable<TElement>, который мы можем вернуть либо как IOrderedEnumerable<TElement>, либо IEnumerable<T> (или IEnumerable или object).

Вопрос заключается в том, что это IOrderedEnumerable, неотъемлемо связанный с целью метода, или это просто артефакт реализации?

Если бы у нас был метод ReturnProducts, который произошел, чтобы упорядочить по цене как часть реализации удаления случаев, когда один и тот же продукт предлагался дважды для разных цен, тогда он должен возвращать IEnumerable<Product>, потому что вызывающие абоненты не должны заботиться о том, чтобы он приказал, и, конечно же, не должен зависеть от него.

Если бы у нас был метод ReturnProductsOrderedByPrice, где упорядочение было частью его цели, тогда мы должны вернуть IOrderedEnumerable<Product>, потому что это более близко относится к его назначению и может разумно ожидать, что вызов CreateOrderedEnumerable, ThenBy или ThenByDescending на нем (это единственное, что действительно предлагает), и не иметь этого сломанного последующим изменением к реализации.

Изменить: я пропустил вторую часть этого.

Как насчет того, что репозиторий обертывает хранимую процедуру с помощью предложение ORDER BY. Должен ли репозиторий возвращать IOrderedEnumerable? И как это будет достигнуто?

Это довольно хорошая идея, когда это возможно (или, возможно, IOrderedQueryable<T>). Однако это не просто.

Во-первых, вы должны быть уверены, что ничто после ORDER BY не может отменить заказ, это может быть не тривиально.

Во-вторых, вы не должны отменять это упорядочение при вызове CreateOrderedEnumerable<TKey>().

Например, если элементы с полями A, B, C и D возвращаются из того, что использовало ORDER BY A DESCENDING, B, что привело к возврату типа с именем MyOrderedEnumerable<El>, который реализует IOrderedEnumerable<El>. Тогда необходимо сохранить то, что A и B - поля, которые были упорядочены. Вызов CreateOrderedEnumerable(e => e.D, Comparer<int>.Default, false) (который также вызывает вызов ThenBy и ThenByDescending) должен принимать группы элементов, которые одинаково сравниваются для A и B, теми же правилами, для которых они были возвращены базой данных (сопоставление сопоставлений между базами данных и .NET может быть трудным), и только внутри этих групп он должен затем упорядочить в соответствии с cmp.Compare(e0.D, e1.D).

Если бы вы могли это сделать, это могло бы быть очень полезно, и было бы вполне уместно, чтобы возвращаемый тип был IOrderedEnumerable, если бы предложения ORDER BY присутствовали во всех запросах, используемых всеми вызовами.

В противном случае IOrderedEnumerable будет ложью - так как вы не смогли выполнить контракт, который он предлагает, и он был бы менее бесполезным.

Ответ 3

IMO IOrderedEnumerable и IOrderedCollection могли бы быть очень полезными для операций высокого уровня, которые будут работать со списками и массивами, но не с наборами, однако каким-то образом List не наследует его, поэтому он потерял эту цель. Теперь он полезен только для тех мыслей, которые вы показали во второй части вашего вопроса (по порядку и т.д.)