何时返回 IOrderedEnumerable?
是否应该将 IOrderedEnumerable 用作纯粹用于语义值的返回类型?
例如,当在表示层中消费模型时,我们如何知道集合是否需要排序或已经排序?
如果存储库使用 ORDER BY
子句包装存储过程,该怎么办?存储库应该返回 IOrderedEnumerable 吗?如何实现这一目标?
Should IOrderedEnumerable
be used as a return type purely for semantic value?
For example, when consuming a model in the presentation layer, how can we know whether the collection requires ordering or is already ordered?
What about in the case that a repository wraps a stored procedure with an ORDER BY
clause. Should the repository return IOrderedEnumerable
? And how would that be achieved?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我认为这不是一个好主意:
如果你不知道一个序列是按哪个键排序的,那么知道它是有序的又有什么意义呢?
IOrderedEnumerable
接口的要点是能够添加辅助排序条件,如果您不知道主要条件是什么,则这没有多大意义。这没有道理。正如我已经说过的,
IOrderedEnumerable
用于添加辅助排序条件,但是当存储过程返回数据时,数据已经排序,此时添加辅助排序条件为时已晚。您所能做的就是将其完全重新排序,因此对结果调用ThenBy
不会产生预期的效果。I don't think it would be a good idea:
What is the point in knowing that a sequence is ordered if you don't know by which key it is ordered? The point of the
IOrderedEnumerable
interface is to be able to add a secondary sort criteria, which doesn't make much sense if you don't know what is the primary criteria.This doesn't make sense. As I already said,
IOrderedEnumerable
is used to add a secondary sort criteria, but when the data is returned by the stored procedure, it is already sorted and it's too late to add a secondary sort criteria. All you can do is re-sort it completely, so callingThenBy
on the result wouldn't have the expected effect.正如 Thomas 指出的,知道一个对象是一个 IOrderedEnumerable 只告诉我们它是以某种方式排序的,而不是它以我们想要维护的方式排序的。
还值得注意的是,返回类型将影响覆盖和编译能力,但不会影响运行时检查:
因此,如果您遇到可能存在
IEnumerable
或的情况IOrderedEnumerable
根据具体情况返回给调用者,该变量将键入为IEnumerable
以及来自返回类型丢失。同时,无论返回类型是什么,调用者都能够确定该类型是否真的是IOrderedEnumerable
。不管怎样,返回类型并不重要。
返回类型的权衡在于调用者的实用性与被调用者的灵活性之间。
考虑一个当前以
return currentResults.ToList()
结尾的方法。可能的返回类型如下:List
IList
ICollection
IEnumerable
>IList
ICollection
IEnumerable
object
让我们排除对象和非泛型类型现在不太可能有用(在它们有用的情况下,使用它们可能是明智的决定)。剩下:
List
IList
ICollection
IEnumerable
上面的我们去的列表越多,我们就越方便调用者使用该类型公开的功能,而下面的类型未公开这些功能。列表越靠下,我们为被调用者提供的灵活性就越大,以便将来更改实现。因此,理想情况下,我们希望在方法的目的上下文中尽可能位于列表的最高位置(向调用者公开有用的功能,并减少创建新集合以提供我们已经提供的功能的情况),但不更高(以允许未来的变化)。
因此,回到我们的例子,我们有一个
IOrderedEnumerable
,我们可以将其作为IOrderedEnumerable
或IEnumerable
返回。 code> (或IEnumerable
或object
)。问题是,这是一个 IOrderedEnumerable 本质上与该方法的目的相关的事实,还是它仅仅是一个实现制品?
如果我们有一个方法
ReturnProducts
碰巧按价格排序,作为删除以不同价格提供两次相同产品的情况的实现的一部分,那么它应该返回IEnumerable;
,因为调用者不应该关心它是否是有序的,当然也不应该依赖它。如果我们有一个方法
ReturnProductsOrderedByPrice
,其中排序是其目的的一部分,那么我们应该返回IOrderedEnumerable
,因为这与其目的更密切相关,并且可以合理地期望对其调用CreateOrderedEnumerable
、ThenBy
或ThenByDescending
(这是它真正提供的唯一功能)并且不会因后续的实施更改而破坏这一点。编辑:我错过了第二部分。
如果可能的话(或者可能是
IOrderedQueryable
),这是一个非常好的主意。然而,事情并不简单。首先,您必须确保
ORDER BY
后面的任何内容都不能撤消排序,这可能不是微不足道的。其次,您不能在调用
CreateOrderedEnumerable()
时撤消此顺序。例如,如果带有字段.Default, false) 的调用(这也是
A
、B
、C
和D
的元素从使用 < code>ORDER BY A DESCENDING, B 导致返回名为MyOrderedEnumerable
的类型,该类型实现IOrderedEnumerable
。然后,必须存储A
和B
是排序字段的事实。对 CreateOrderedEnumerable(e => eD, ComparerThenBy
和ThenByDescending
的调用into) 必须按照数据库返回它们的相同规则(匹配数据库和.NET 可能很难),并且只有在这些组内才必须根据cmp.Compare(e0.D, e1.D)
进行排序。如果您能做到这一点,它可能会非常有用,并且如果
ORDER BY
子句出现在由所有通话。否则,
IOrderedEnumerable
将是一个谎言 - 因为你无法履行它提供的合同 - 而且它几乎毫无用处。As Thomas points out, knowing that an object is an
IOrderedEnumerable
tells us only that it's been ordered in some way, not that it's been ordered in a way that we will want to maintain.It's also worth noting that the return type will influence overrides and compile-ability, but not runtime checks:
Because of this, if you have a case where there could be either
IEnumerable<T>
orIOrderedEnumerable<T>
returned to the caller depending on circumstance, the variable will be typed asIEnumerable<T>
and the information from the return type lost. Meanwhile, no matter what the return type, the caller will be able to determine if the type is reallyIOrderedEnumerable<T>
.Either way, return type didn't really matter.
The trade-off with return types are between utility to the caller vs flexibility to the callee.
Consider a method that currently ends with
return currentResults.ToList()
. The following return types are possible:List<T>
IList<T>
ICollection<T>
IEnumerable<T>
IList
ICollection
IEnumerable
object
Let's exclude object and the non-generic types right now as unlikely to be useful (in cases where they would be useful, they are probably no-brainer decisions to use). This leaves:
List<T>
IList<T>
ICollection<T>
IEnumerable<T>
The higher up the list we go, the more convenience we give the caller to make use of the functionality exposed by that type, that is not exposed by the type below. The lower down the list we go, the more flexibility we give to the callee to change the implementation in the future. Ideally therefore, we want to go as high up the list as makes sense in the context of the method's purpose (to expose useful functionality to the caller, and reduce cases where new collections are created to offer functionality we were already offering) but no higher (to allow for future changes).
So, back to our case where we have an
IOrderedEnumerable<TElement>
that we can return as either anIOrderedEnumerable<TElement>
or anIEnumerable<T>
(orIEnumerable
orobject
).The question is, is the fact that this is an
IOrderedEnumerable
inherently related to the purpose of the method, or is it merely an implementation artefact?If we had a method
ReturnProducts
that happened to order by price to as part of the implementation of removing cases where the same product was offered twice for different prices, then it should returnIEnumerable<Product>
, because callers shouldn't care that it's ordered, and certainly shouldn't depend upon it.If we had a method
ReturnProductsOrderedByPrice
where the ordering was part of its purpose, then we should returnIOrderedEnumerable<Product>
, because this relates more closely to its purpose, and may reasonably expect that callingCreateOrderedEnumerable
,ThenBy
orThenByDescending
on it (the only things this really offers) and not have this broken by a subsequent change to the implementation.Edit: I missed the second part of this.
That is quite a good idea when possible (or perhaps
IOrderedQueryable<T>
). However, it's not simple.First, you have to be sure that nothing subsequent to the
ORDER BY
could have undone the ordering, this may not be trivial.Secondly, you have to not undo this ordering in a call to
CreateOrderedEnumerable<TKey>()
.For example, if elements with fields
A
,B
,C
andD
are being returned from something that usedORDER BY A DESCENDING, B
resulting in the return of a type calledMyOrderedEnumerable<El>
that implementsIOrderedEnumerable<El>
. Then, the fact thatA
andB
are the fields that were ordered on must be stored. A call toCreateOrderedEnumerable(e => e.D, Comparer<int>.Default, false)
(which is also whatThenBy
andThenByDescending
call into) must take groups of elements that compare equally forA
andB
, by the same rules for which they were returned by the database (matching collations between databases and .NET can be hard), and only within those groups must it then order according tocmp.Compare(e0.D, e1.D)
.If you could do that, it could be very useful, and it would totally be appropriate that the return type were
IOrderedEnumerable
ifORDER BY
clauses would be present on all queries used by all calls.Otherwise,
IOrderedEnumerable
would be a lie - since you couldn't fulfil the contract it offers - and it would be less than useless.IMO
IOrderedEnumerable
和IOrderedCollection
对于高级操作非常有用,即在列表和数组上工作,但在集合上不起作用,但是不知何故 List 不继承它,因此它失去了那个目的。它现在仅对您在问题第二部分中展示的想法有用(按顺序等)IMO
IOrderedEnumerable
and anIOrderedCollection
could have been very useful for high level operations that will ie work on lists and arrays but not on sets, however somehow List does not inherit it so it lost that purpose. It is now only useful for the kind of thinkgs you showed in the second part of your question (order by etc)