37

Say there is an extension method to order an IQueryable based on several types of Sorting (i.e. sorting by various properties) designated by a SortMethod enum.

public static IOrderedEnumerable<AClass> OrderByX(this IQueryable<AClass> values,
    SortMethod? sortMethod)
{ 
    IOrderedEnumerable<AClass> queryRes = null;
    switch (sortMethod)
    {
        case SortMethod.Method1:
            queryRes = values.OrderBy(a => a.Property1);
            break;
        case SortMethod.Method2:
            queryRes = values.OrderBy(a => a.Property2);
            break;
        case null:
            queryRes = values.OrderBy(a => a.DefaultProperty);
            break;
        default:
            queryRes = values.OrderBy(a => a.DefaultProperty);
            break;
    }
    return queryRes;
}

In the case where sortMethod is null (i.e. where it is specified that I don't care about the order of the values), is there a way to instead of ordering by some default property, to instead just pass the IEnumerator values through as "ordered" without having to perform the actual sort?

I would like the ability to call this extension, and then possibly perform some additional ThenBy orderings.

Heisenbug
  • 38,762
  • 28
  • 132
  • 190
NominSim
  • 8,447
  • 3
  • 28
  • 38
  • you can order them by the index in their original collection – Sten Petrov Jan 18 '13 at 17:19
  • 1
    @StenPetrov Note that doing that would mean any subsequence `ThenBy` calls wouldn't change the ordering, as opposed to a "noop" sort that would instead be overriden by the `ThenBy` call. Both might be options. – Servy Jan 18 '13 at 17:26

3 Answers3

55

All you need to do for the default case is:

queryRes = values.OrderBy(a => 1);

This will effectively be a noop sort. Because the OrderBy performs a stable sort the original order will be maintained in the event that the selected objects are equal. Note that since this is an IQueryable and not an IEnumerable it's possible for the query provider to not perform a stable sort. In that case, you need to know if it's important that order be maintained, or if it's appropriate to just say "I don't care what order the result is, so long as I can call ThenBy on the result).

Another option, that allows you to avoid the actual sort is to create your own IOrderedEnumerable implementation:

public class NoopOrder<T> : IOrderedEnumerable<T>
{
    private IQueryable<T> source;
    public NoopOrder(IQueryable<T> source)
    {
        this.source = source;
    }

    public IOrderedEnumerable<T> CreateOrderedEnumerable<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer, bool descending)
    {
        if (descending)
        {
            return source.OrderByDescending(keySelector, comparer);
        }
        else
        {
            return source.OrderBy(keySelector, comparer);
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return source.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return source.GetEnumerator();
    }
}

With that your query can be:

queryRes = new NoopOrder<AClass>(values);

Note that the consequence of the above class is that if there is a call to ThenBy that ThenBy will effectively be a top level sort. It is in effect turning the subsequent ThenBy into an OrderBy call. (This should not be surprising; ThenBy will call the CreateOrderedEnumerable method, and in there this code is calling OrderBy, basically turning that ThenBy into an OrderBy. From a conceptual sorting point of view, this is a way of saying that "all of the items in this sequence are equal in the eyes of this sort, but if you specify that equal objects should be tiebroken by something else, then do so.

Another way of thinking of a "no op sort" is that it orders the items based in the index of the input sequence. This means that the items are not all "equal", it means that the order input sequence will be the final order of the output sequence, and since each item in the input sequence is always larger than the one before it, adding additional "tiebreaker" comparisons will do nothing, making any subsequent ThenBy calls pointless. If this behavior is desired, it is even easier to implement than the previous one:

public class NoopOrder<T> : IOrderedEnumerable<T>
{
    private IQueryable<T> source;
    public NoopOrder(IQueryable<T> source)
    {
        this.source = source;
    }

    public IOrderedEnumerable<T> CreateOrderedEnumerable<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer, bool descending)
    {
        return new NoopOrder<T>(source);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return source.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return source.GetEnumerator();
    }
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • 2
    @Servy I thought of this seconds after I posted the question, and it certainly seems to be the way to go, however intuitively this seems to perform a "sort" assigning the value `1` to every class (intuitively of complexity O(n)). Or is the compiler smart enough to not have to "sort" the queryable? – NominSim Jan 18 '13 at 17:38
  • @NominSim The compiler couldn't optimize it away, but the query provider might. If it's working it's way back to, for example, a SQL based database my guess is it's smart enough to realize it's sorting on a constant. In any case, see edit for another solution. – Servy Jan 18 '13 at 17:42
  • 1
    @NominSim As I've said in comments, odds are the SQL database will be able to optimize out the noop query, even though a Linq-to-objects query couldn't. In the event your testing indicates otherwise, that's what the other solution is there for. – Servy Jan 18 '13 at 17:50
  • This is *not* a noop sort. A call to Enumerable.OrderBy(x=>1) effectively tells the OrderedEnumerable implementation that all elements have the same "top-level" sort value. Any projection specified in a further ThenBy() call will be treated as the "top level" of the enumerable elements, and the lowest element of *that* projection will appear first in the ordered list regardless of what the collection had originally been ordered by. Thus, you can't use ThenBy() on an enumerable that's had OrderBy(x=>1) called on it, and if you can't do that, why do you want IOrderedEnumerable at all? – KeithS Jan 18 '13 at 18:24
  • @KeithS `"Thus, you can't use ThenBy() on an enumerable that's had OrderBy(x=>1) called on it"` Sure you can. If you do, it will behave as if it's a top level sort. That's the *intended behavior* of this answer. The intended behavior was *not* that the input sequence would remain in that order, regardless of any subsequence `ThenBy` calls. (That could be done though, if that's what the OP states he wants.) Note that in such a case that you're proposing, it really *would* be pointless to call `ThenBy`, as it could never change the order of the items. – Servy Jan 18 '13 at 18:30
  • The point remains that the call provides no meaningful information about what the elements are currently ordered by, meaning that this function, which would be expected to somehow divine the current ordering of the source enumerable, would not. You're calling a method to order the enumerable that actually doesn't, and claiming it will be a noop when it actually will do quite a bit of work (no swaps, but it still performs nlogn comparisons) – KeithS Jan 18 '13 at 18:38
  • `"the call provides no meaningful information about what the elements are currently ordered by"` But it does. They're ordered by a constant, meaning they are all effectively "equal" in magnitude. That's not "wrong" for a comparer. Oh, and no, it doesn't perform n*log(n) operations. It is O(1). It avoids the sort entirely. (That is, the second option, the `OrderBy(x=>1)` does a sort, which is why the second option was added, it avoids it.) Oh, and since you *really* want to see the other possible implementation of a noop sort, I edited it in. – Servy Jan 18 '13 at 18:47
  • 1
    Prove that OrderBy(x=>1) actually does cause an O(1) early exit and I will remove my downvote and upvote this answer. You won't be able to, because when the enumerable is *actually* evaluated and the sort is *actually* performed (OrderBy(), like most Linq methods, defers execution so calling `collection.OrderBy(x=>1)` *is* O(1), but is also quite useless), it must at least calculate N projections of the lambda, one for each element, to see that it's "sorted". It also requires N space, because the enumerable must be slurped into an array to sort, even if the actual sort is in-place. – KeithS Jan 18 '13 at 18:58
  • @KeithS `"Prove that OrderBy(x=>1) actually does cause an O(1) early exit"` It doesn't, and I've specifically stated that it doesn't. As I said in my previous comment, if it's a Linq-to-objects query it will perform the sort, and thus not be O(1). If it's going to a query provider it depends on that query provider. What I said was O(1) was the following implementation in which I implement `IOrderedEnumerable`. Both of those are O(1), no matter what. – Servy Jan 18 '13 at 19:11
  • You stated that "It is O(1). It avoids the sort entirely". That was in reference to `OrderBy(x=>1)`. That is a false statement and that's what I called you out for. I was not referring to your NoopOrder<> class, even if you were trying to switch horses on me. BTW, that implementation has its own problems, primarily in that it implicitly converts from IQueryable to IEnumerable, forcing query evaluation at that point in the chain. OrderBy() overloads for IQueryable and IEnumerable are distinct, and the Queryable overload does not force evaluation. – KeithS Jan 18 '13 at 19:19
  • @KeithS So did you just stop reading right at that point? The sentence immediately following the quote specifically says that the preceding statement applies to the second solution, and admits that the first solution does do a sort. I went out of my way to add that sentance because I knew it would be ambiguous otherwise. This is also addressed in the answer itself, in which I state why I provide the second answer to begin with. – Servy Jan 18 '13 at 19:23
  • @KeithS As for the `IEnumerable`/`IQueryable`, the method that the OP is using returns an `IOrderedEnumerable`, not an `IOrderedQueryable`, so even if my class returned an `IOrderedQueryable`, his method would convert it to an enumeration anyway. – Servy Jan 18 '13 at 19:28
5

If you return always the same index value you will get an IOrderedEnumerable that preserve the original list order:

case null:
     queryRes = values.OrderBy(a => 1);
     break;

Btw I don't think this is a right thing to do. You will get a collection that is supposted to be ordered but actually it is not.

Heisenbug
  • 38,762
  • 28
  • 132
  • 190
  • 3
    I disagree with your comment that it's inappropriate to use this. I can imagine context's in which it's appropriate. Imagine a user interface in which they are allowed to specify fields to order on, in which "none" is one of the appropriate options. This is how you would handle that. – Servy Jan 18 '13 at 17:34
  • No you wouldnt. The none logic would have been filtered at the UI layer or the layer beneath and not reach the sort code. Anyway, I stand by my answer, that had to be said. What you said maybe what the OP wants. – manojlds Jan 18 '13 at 17:37
  • 1
    @Servy: maybe you are right. I think here it's a bit philosophical. If you consider the disorder a type of order than it's a possible way to go. If disorder is a negation of order that the request should be filtered like suggested by manojlds. That's just my point of view. Where's Jon Skeet? He could show us the light :) – Heisenbug Jan 18 '13 at 17:40
  • @Heisenbug Servy's description pretty much perfectly described my situation. I created a generic method for when a DB query happens to return the data the user is interested in, and in certain circumstances they can sort that data. I think it ends up being a philosophical point with the caveat that the sort *shouldn't actually be performed* (whether a no-op or not) if the user doesn't specify it. You did get +1 from me though. – NominSim Jan 18 '13 at 17:53
-2

Bottom line, IOrderedEnumerable exists solely to provide a grammar structure to the OrderBy()/ThenBy() methods, preventing you from trying to start an ordering clause with ThenBy(). process. It's not intended to be a "marker" that identifies the collection as ordered, unless it was actually ordered by OrderBy(). So, the answer is that if the sorting method being null is supposed to indicate that the enumerable is in some "default order", you should specify that default order (as your current implementation does). It's disingenuous to state that the enumerable is ordered when in fact it isn't, even if, by not specifying a SortingMethod, you are inferring it's "ordered by nothing" and don't care about the actual order.

The "problem" inherent in trying to simply mark the collection as ordered using the interface is that there's more to the process than simply sorting. By executing an ordering method chain, such as myCollection.OrderBy().ThenBy().ThenByDescending(), you're not actually sorting the collection with each call; not yet anyway. You are instead defining the behavior of an "iterator" class, named OrderedEnumerable, which will use the projections and comparisons you define in the chain to perform the sorting at the moment you need an actual sorted element.

Servy's answer, stating that OrderBy(x=>1) is a noop and should be optimized out of SQL providers ignores the reality that this call, made against an Enumerable, will still do quite a bit of work, and that most SQL providers in fact do not optimize this kind of call; OrderBy(x=>1) will, in most Linq providers, produce a query with an "ORDER BY 1" clause, which not only forces the SQL provider to perform its own sorting, it will actually result in a change to the order, because in T-SQL at least "ORDER BY 1" means to order by the first column of the select list.

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • 2
    Your third paragraph is just *wrong*. They don't rely on that interface having that specific concrete class; they just program against the interface (which is the whole point of having interfaces). If you can create your own interface implementation that meets the contract and performs behavior you expect, that's acceptable, and that's what I did. You say my answer won't work, but it will. The behavior you describe is *exactly* the behavior I * intended* when I wrote it. Since it behaves exactly as intended, it does in fact "work". – Servy Jan 18 '13 at 18:25
  • 1
    As for your last sentence, I *do* indicate what it is ordering by. It's ordering by the constant `1`. The result of that is that all objects are the same. The *intended* side effect of that is that any subsequent `ThenBy` calls will effectively appear to be top level ordering calls, and if there is no `ThenBy` calls that the input sequence will be returned as if it wasn't sorted. – Servy Jan 18 '13 at 18:27
  • But was it what the OP intended? The OP says he wants to perform ThenBy() ordering calls on the enumerable, and have those calls affect the initial sort while preserving those top levels. If he does, after having called OrderBy(x=>1), *that* projection will be treated as the top level of the sort criteria because the level above that provides no meaningful comparison (all elements are "1"s). – KeithS Jan 18 '13 at 18:30
  • 1
    Yes, and when he does call `ThenBy` it's possible he *wants* it to be treated as the top level sort, instead of being ignored. If he states that he *does* want that subsequent call to effectively be ignored, and that the result of this noopSort should return the input sequence, in the order give, regardless of any "ThenBy" calls, then that can be done. He has yet to state that as his requirement. – Servy Jan 18 '13 at 18:32
  • Oh, and this "answer" isn't an answer to a question, it's just a comment on another answer (mine) that doesn't fit in a comment. – Servy Jan 18 '13 at 18:32
  • Changed to be more of an answer. I still categorically disagree with your assertion that using OrderBy(x=>1) is in any way a noop or that the database will optimize it out. Both are *dead* wrong. – KeithS Jan 18 '13 at 18:52
  • To address your new last paragraph. I never said that the query provider would optimize out the ordering by a constant. In fact, I highly doubt they would. What I said, and still believe to be at last a possibility, is that the database will be able to interpret that the query is ordering by a constant and not perform a sort on it. Whether it can will be entirely dependant on implementations, and note I never said it *would*, just that it's something worth testing for a specific situation. If it doesn't work, that's exactly why I provided my second implementation which *will* work. – Servy Jan 18 '13 at 19:14
  • At this point every possible criticism of my answer has been addressed. There is one solution that is easiest and may work, and then two solutions that are more code, but each *will* work, providing each of the two possible semantics for a noop sort given that the original post is ambiguous on which is appropriate. At this point it seems you're just being stubborn and aren't willing to admit the correctness of the answer. – Servy Jan 18 '13 at 19:16
  • Oh my G-d. `As I've said in comments, odds are the SQL database will be able to optimize out the noop query`. That what you said, it is *patently* false, and you are trying to backtrack by saying you were never wrong, you just meant something different. I'm willing to accept that your answer was unclear (in fact I openly state it) but I will not apologize for calling it out. I *am* being stubborn, because your answer as originally written was misleading to the point of being damagingly incorrect, and I am going to make you address my concerns, by a series of "page 6 retractions" if I have to. – KeithS Jan 18 '13 at 19:26
  • First, that's a comment, not an answer. The answer is not unclear or false in any way. Second, do you have any evidence that it's not possible for a query provider to optimize out that ordering? It's a perfectly reasonable optimization to add to the query optimizer in the case that the ordering is on a constant. I could see it not added, which I why I never said it *would* work, but it's certainly not *impossible*. In any case, the answer as it stands is *very* clear. If the first, simple, solution doesn't work, you use the slightly more complex but guaranteed to work solution. – Servy Jan 18 '13 at 19:33
  • "ORDER BY 1" is a valid, meaningful clause in MSS, Oracle and MySQL. It means to order by the first column of the Select list. It is *not* something that would be ignored. So, if anything would ignore OrderBy(x=>1), it would be the Linq provider. I know for a fact that NHibernate does not ignore it; it plugs in the constant integer 1, and I can try L2S and EF for you if you really want me to be thorough. The behavior produced by OrderBy(x=>1) is therefore most likely to be a sort by the first column, which is *not* a "noop sort". – KeithS Jan 18 '13 at 19:46
  • 1
    And what indication do you have that `OrderBy(x=>1)` results in `Order By 1` in SQL? The query provider need not ignore it, it needs to simply generate a query that indicates it's sorting on a literal value as opposed to ordering on a column. Also note that there are different flavors of SQL. I know enough to know that it's almost certainly *possible* to define that behavior. I don't know if it actually *does*, which is exactly what I've said everywhere. And as I've said countless times, it just *doesn't matter*. If it doesn't work, you use one of the other implementations provided. – Servy Jan 18 '13 at 19:53
  • 1
    I'm afraid you are wrong on one point. OrderBy(x =>1) doesn't translate to "Order By 1" in MSS. It translate into "Order By @linq_param1" where the @linq_param1 as a value of 1. Also, I agree with you on SQL engine's optimization. I've never seem such thing with MSS, I beleive SQL engine will perform the sort anyway. – AXMIM Jul 12 '16 at 19:48