何时返回IOrderedEnumerable
本文关键字:IOrderedEnumerable 返回 何时 | 更新日期: 2023-09-27 18:17:51
是否应该将IOrderedEnumerable
用作纯粹用于语义值的返回类型?
例如,当在表示层中使用模型时,我们如何知道集合是需要排序还是已经排序?
如果存储库用ORDER BY
子句包装存储过程,情况又会怎样呢?存储库应该返回IOrderedEnumerable
吗?如何实现这一目标?
IOrderedEnumerable应该用作纯粹的语义值的返回类型吗?
例如,当在表示层中使用模型时,我们如何知道集合是需要排序还是已经排序?
如果你不知道序列是按哪个键排序的,那么知道序列是有序的又有什么意义呢?IOrderedEnumerable
接口的意义在于能够添加二级排序标准,如果您不知道什么是主要标准,那么这没有多大意义。
如果存储库用ORDER BY子句包装存储过程呢?存储库应该返回IOrderedEnumerable吗?如何实现这一目标?
这没有意义。正如我已经说过的,IOrderedEnumerable
用于添加二级排序条件,但是当存储过程返回数据时,数据已经排序了,再添加二级排序条件就太晚了。您所能做的就是完全重新排序,所以对结果调用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>
-
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)
进行排序。
如果你能做到这一点,它将是非常有用的,如果ORDER BY
子句将出现在所有调用使用的所有查询中,那么返回类型是IOrderedEnumerable
将是完全合适的。
否则,IOrderedEnumerable
将是一个谎言——因为你无法履行它提供的合同——它将是无用的。
IMO IOrderedEnumerable
和IOrderedCollection
对于将在列表和数组上工作而不是在集合上工作的高级操作可能非常有用,但是不知何故List没有继承它,因此它失去了这个目的。它现在只适用于你在问题的第二部分所展示的那种想法(按等排序)