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

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

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

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

2 ответа

Решение

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

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

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

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

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

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

Как указывает Томас, зная, что объект является 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(), Возможны следующие типы возврата:

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

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

Другие вопросы по тегам