【发布时间】:2012-01-21 01:24:09
【问题描述】:
是否应该将IOrderedEnumerable 用作纯粹用于语义值的返回类型?
例如,在表示层消费一个模型时,我们如何知道集合是需要排序的还是已经排序的?
如果存储库使用ORDER BY 子句包装存储过程会怎样。存储库是否应该返回IOrderedEnumerable?那将如何实现?
【问题讨论】:
标签: c# .net linq semantics iorderedenumerable
是否应该将IOrderedEnumerable 用作纯粹用于语义值的返回类型?
例如,在表示层消费一个模型时,我们如何知道集合是需要排序的还是已经排序的?
如果存储库使用ORDER BY 子句包装存储过程会怎样。存储库是否应该返回IOrderedEnumerable?那将如何实现?
【问题讨论】:
标签: c# .net linq semantics iorderedenumerable
我认为这不是一个好主意:
是否应该将 IOrderedEnumerable 用作纯粹用于语义值的返回类型?
例如,在表示层消费一个模型时,我们如何知道该集合是需要排序的还是已经排序的?
如果您不知道它是按哪个键排序的,那么知道一个序列是有序的有什么意义? IOrderedEnumerable 接口的重点是能够添加辅助排序条件,如果您不知道主要条件是什么,这没有多大意义。
如果存储库使用 ORDER BY 子句包装存储过程,该怎么办。存储库是否应该返回 IOrderedEnumerable?那将如何实现?
这没有意义。正如我已经说过的,IOrderedEnumerable 用于添加辅助排序条件,但是当存储过程返回数据时,它已经排序,添加辅助排序条件已经来不及了。您所能做的就是完全重新排序,因此在结果上调用ThenBy 不会产生预期的效果。
【讨论】:
A 上订购的方法有可能根据该知识提供CreateOrderedEnumerable() ( ThenBy 调用)。不过,在更一般的情况下要复杂得多,而且实际上可能不是很有用。
正如 Thomas 所指出的,知道一个对象是 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>IListICollectionIEnumerableobject让我们现在排除对象和非泛型类型,因为它们不太可能有用(在它们有用的情况下,它们可能是不费吹灰之力的决定)。这会留下:
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>() 时不要撤消此排序。
例如,如果从使用 ORDER BY A DESCENDING, B 的对象返回具有字段 A、B、C 和 D 的元素,则会返回一个名为 MyOrderedEnumerable<El> 的类型,该类型实现了 @987654362 @。然后,必须存储A 和B 是排序的字段这一事实。对CreateOrderedEnumerable(e => e.D, Comparer<int>.Default, false) 的调用(这也是ThenBy 和ThenByDescending 的调用)必须采用与A 和B 进行同等比较的元素组,其规则与数据库返回的规则相同(数据库和 .NET 之间的匹配排序规则可能很难),并且只有在这些组中,它才必须根据 cmp.Compare(e0.D, e1.D) 进行排序。
如果你能做到这一点,它可能会非常有用,如果 ORDER BY 子句出现在所有调用使用的所有查询中,则返回类型为 IOrderedEnumerable 是完全合适的。
否则,IOrderedEnumerable 将是一个谎言——因为你无法履行它提供的合同——而且它不会毫无用处。
【讨论】:
IMO IOrderedEnumerable 和IOrderedCollection 对于高级操作可能非常有用,即在列表和数组上工作但在集合上工作,但不知何故 List 没有继承它,所以它失去了那个目的。它现在仅对您在问题的第二部分中显示的那种想法有用(按等排序)
【讨论】: