36

SortMethod假设有一种扩展方法可以根据枚举指定的几种排序类型(即按各种属性排序)对 IQueryable 进行排序。

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

在 where sortMethodis的情况下null(即指定我不关心值的顺序),有没有一种方法可以代替某些默认属性进行排序,而只是将IEnumerator值作为“有序”传递而没有必须执行实际排序?

我希望能够调用这个扩展,然后可能执行一些额外ThenBy的命令。

4

3 回答 3

53

对于默认情况,您需要做的就是:

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

这实际上是一个 noop 排序。因为 OrderBy 执行稳定的排序,所以在所选对象相等的情况下将保持原始顺序。请注意,由于这是一个IQueryable而不是一个IEnumerable,因此查询提供程序可能无法执行稳定的排序。在这种情况下,您需要知道保持顺序是否重要,或者是否适合只说“我不在乎结果是什么顺序,只要我可以调用ThenBy结果即可)。

另一个允许您避免实际排序的选项是创建自己的IOrderedEnumerable实现:

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();
    }
}

这样您的查询可以是:

queryRes = new NoopOrder<AClass>(values);

请注意,上述类的结果是,如果调用该类ThenByThenBy有效地进行顶级排序。它实际上是把后续ThenBy变成了一个OrderBy电话。(这应该不足为奇;ThenBy将调用该CreateOrderedEnumerable方法,并且在其中此代码正在调用OrderBy,基本上将其ThenBy转换为OrderBy。从概念排序的角度来看,这是一种说法,即“此序列中的所有项目都是在这种人眼中是相等的,但是如果您指定相等的对象应该被其他东西打破,那么就这样做。

“无操作排序”的另一种思考方式是它根据输入序列的索引对项目进行排序。这意味着项目并非全部“相等”,这意味着订单输入序列是输出序列的最终订单,并且由于输入序列中的每个项目总是大于它之前的项目,因此添加了额外的“tiebreaker” " 比较将无济于事,使任何后续ThenBy调用都毫无意义。如果需要这种行为,它甚至比前一种更容易实现:

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();
    }
}
于 2013-01-18T17:27:45.750 回答
5

如果您始终返回相同的索引值,您将获得一个保留原始列表顺序的 IOrderedEnumerable:

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

顺便说一句,我认为这不是正确的做法。您将获得一个本应订购但实际上并非如此的集合。

于 2013-01-18T17:26:58.820 回答
-1

归根结底,IOrderedEnumerable 的存在只是为 OrderBy()/ThenBy() 方法提供语法结构,防止您尝试使用 ThenBy() 启动排序子句。过程。它并非旨在将集合标识为已排序的“标记”,除非它实际上是由 OrderBy() 排序的。所以,答案是,如果排序方法为 null 应该表明可枚举是某种“默认顺序”,那么您应该指定该默认顺序(就像您当前的实现一样)。说可枚举是有序的,而实际上它不是,这是不诚实的,即使通过不指定 SortingMethod,您推断它是“无序的”并且不关心实际的顺序。

尝试使用接口简单地将集合标记为已排序所固有的“问题”是,该过程不仅仅是简单的排序。通过执行排序方法链,例如myCollection.OrderBy().ThenBy().ThenByDescending(),您实际上并没有在每次调用时对集合进行排序;反正还没有。相反,您正在定义名为 OrderedEnumerable 的“迭代器”类的行为,它将使用您在链中定义的投影和比较来在您需要实际排序元素时执行排序。

Servy 的回答指出 OrderBy(x=>1) 是一个 noop 并且应该从 SQL 提供程序中进行优化忽略了这样一个事实,即针对 Enumerable 进行的这个调用仍然会做很多工作,并且大多数 SQL 提供程序在事实上不要优化这种调用;在大多数 Linq 提供程序中,OrderBy(x=>1) 将生成一个带有“ORDER BY 1”子句的查询,这不仅会强制 SQL 提供程序执行自己的排序,实际上还会导致顺序发生变化,因为在 T-SQL 中至少“ORDER BY 1”意味着按选择列表的第一列排序。

于 2013-01-18T18:20:12.190 回答