何时返回 IOrderedEnumerable?

发布于 2024-12-21 14:25:32 字数 182 浏览 1 评论 0原文

是否应该将 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

吻泪 2024-12-28 14:25:32

我认为这不是一个好主意:

是否应该将 IOrderedEnumerable 用作纯粹用于语义值的返回类型?

例如,在表示层消费模型时,我们如何知道集合是否需要排序或已经排序?

如果你不知道一个序列是按哪个键排序的,那么知道它是有序的又有什么意义呢? IOrderedEnumerable 接口的要点是能够添加辅助排序条件,如果您不知道主要条件是什么,则这没有多大意义。

如果存储库使用 ORDER BY 子句包装存储过程,该怎么办?存储库应该返回 IOrderedEnumerable 吗?如何实现这一目标?

这没有道理。正如我已经说过的,IOrderedEnumerable 用于添加辅助排序条件,但是当存储过程返回数据时,数据已经排序,此时添加辅助排序条件为时已晚。您所能做的就是将其完全重新排序,因此对结果调用 ThenBy 不会产生预期的效果。

I don't think it would be a good idea:

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 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.

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?

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 calling ThenBy on the result wouldn't have the expected effect.

画离情绘悲伤 2024-12-28 14:25:32

正如 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的情况IOrderedEnumerable 根据具体情况返回给调用者,该变量将键入为 IEnumerable 以及来自返回类型丢失。同时,无论返回类型是什么,调用者都能够确定该类型是否真的是IOrderedEnumerable

不管怎样,返回类型并不重要。

返回类型的权衡在于调用者的实用性与被调用者的灵活性之间。

考虑一个当前以 return currentResults.ToList() 结尾的方法。可能的返回类型如下:

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

让我们排除对象和非泛型类型现在不太可能有用(在它们有用的情况下,使用它们可能是明智的决定)。剩下:

  1. List
  2. IList
  3. ICollection
  4. IEnumerable

上面的我们去的列表越多,我们就越方便调用者使用该类型公开的功能,而下面的类型未公开这些功能。列表越靠下,我们为被调用者提供的灵活性就越大,以便将来更改实现。因此,理想情况下,我们希望在方法的目的上下文中尽可能位于列表的最高位置(向调用者公开有用的功能,并减少创建新集合以提供我们已经提供的功能的情况),但不更高(以允许未来的变化)。

因此,回到我们的例子,我们有一个 IOrderedEnumerable,我们可以将其作为 IOrderedEnumerableIEnumerable 返回。 code> (或 IEnumerableobject)。

问题是,这是一个 IOrderedEnumerable 本质上与该方法的目的相关的事实,还是它仅仅是一个实现制品?

如果我们有一个方法 ReturnProducts 碰巧按价格排序,作为删除以不同价格提供两次相同产品的情况的实现的一部分,那么它应该返回 IEnumerable;,因为调用者不应该关心它是否是有序的,当然也不应该依赖它。

如果我们有一个方法 ReturnProductsOrderedByPrice ,其中排序是其目的的一部分,那么我们应该返回 IOrderedEnumerable,因为这与其目的更密切相关,并且可以合理地期望对其调用 CreateOrderedEnumerableThenByThenByDescending(这是它真正提供的唯一功能)并且不会因后续的实施更改而破坏这一点。

编辑:我错过了第二部分。

如果存储库将存储过程包装为
ORDER BY 子句。存储库应该返回 IOrderedEnumerable 吗?
如何实现这一目标?

如果可能的话(或者可能是IOrderedQueryable),这是一个非常好的主意。然而,事情并不简单。

首先,您必须确保 ORDER BY 后面的任何内容都不能撤消排序,这可能不是微不足道的。

其次,您不能在调用 CreateOrderedEnumerable() 时撤消此顺序。

例如,如果带有字段 ABCD 的元素从使用 < code>ORDER BY A DESCENDING, B 导致返回名为 MyOrderedEnumerable 的类型,该类型实现 IOrderedEnumerable。然后,必须存储 AB 是排序字段的事实。对 CreateOrderedEnumerable(e => eD, Comparer.Default, false) 的调用(这也是 ThenByThenByDescending 的调用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:

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.
}

Because of this, if you have a case where there could be either IEnumerable<T> or IOrderedEnumerable<T> returned to the caller depending on circumstance, the variable will be typed as IEnumerable<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 really IOrderedEnumerable<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:

  1. List<T>
  2. IList<T>
  3. ICollection<T>
  4. IEnumerable<T>
  5. IList
  6. ICollection
  7. IEnumerable
  8. 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:

  1. List<T>
  2. IList<T>
  3. ICollection<T>
  4. 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 an IOrderedEnumerable<TElement> or an IEnumerable<T> (or IEnumerable or object).

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 return IEnumerable<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 return IOrderedEnumerable<Product>, because this relates more closely to its purpose, and may reasonably expect that calling CreateOrderedEnumerable, ThenBy or ThenByDescending 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.

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?

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 and D are being returned from something that used ORDER BY A DESCENDING, B resulting in the return of a type called MyOrderedEnumerable<El> that implements IOrderedEnumerable<El>. Then, the fact that A and B are the fields that were ordered on must be stored. A call to CreateOrderedEnumerable(e => e.D, Comparer<int>.Default, false) (which is also what ThenBy and ThenByDescending call into) must take groups of elements that compare equally for A and B, 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 to cmp.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 if ORDER 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.

冷心人i 2024-12-28 14:25:32

IMO IOrderedEnumerableIOrderedCollection 对于高级操作非常有用,即在列表和数组上工作,但在集合上不起作用,但是不知何故 List 不继承它,因此它失去了那个目的。它现在仅对您在问题第二部分中展示的想法有用(按顺序等)

IMO IOrderedEnumerable and an IOrderedCollection 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)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文