Когда возвращать IOrderedEnumerable? - PullRequest
21 голосов
/ 15 декабря 2011

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

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

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

Ответы [ 3 ]

18 голосов
/ 15 декабря 2011

Не думаю, что это было бы хорошей идеей:

Следует ли использовать IOrderedEnumerable в качестве возвращаемого типа исключительно для семантического значения?

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

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

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

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

9 голосов
/ 15 декабря 2011

Как указывает Томас, знание того, что объект является 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>.

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

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

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

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

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

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

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

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

Вопрос в том, является ли это 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 было бы ложью - поскольку вы не могли выполнить контрактэто предлагает - и это было бы менее чем бесполезно.

0 голосов
/ 16 июля 2019

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

...