是否可以在不使用OrderBy的情况下将IEnumerable转换为IOrderedEnumerable?

时间:2013-01-18 17:16:59

标签: c# linq ienumerable iorderedenumerable

假设有一种扩展方法可以根据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;
}

sortMethodnull的情况下(即指定我不关心值的顺序),有没有办法代替按某些默认属性排序,而只是将IEnumerator值传递给“有序”,而不必执行实际的排序?

我希望能够调用此扩展程序,然后可能执行一些额外的ThenBy排序。

3 个答案:

答案 0 :(得分:38)

默认情况下您需要做的就是:

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

请注意,上述课程的结果是,如果调用ThenBy,则ThenBy将有效地成为顶级排序。它实际上将后续ThenBy转变为OrderBy来电。 (这不应该是令人惊讶的; ThenBy会调用CreateOrderedEnumerable方法,并且在此处此代码调用OrderBy,基本上将ThenBy转换为OrderBy从概念分类的角度来看,这是一种说法,“在这种情况下,这一序列中的所有项目都是相同的,但是如果你指定相同的对象应该由其他东西打破,那么这样做

另一种思考“无操作排序”的方法是它根据输入序列的索引对项目进行排序。这意味着项目并非全部“相等”,这意味着订单输入序列是输出序列的最终顺序,并且因为输入序列中的每个项目总是大于一个在它之前,添加额外的“决胜局”比较将不会做任何事情,使任何后续的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();
    }
}

答案 1 :(得分:6)

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

case null:
     queryRes = values.OrderBy(a => 1);
     break;
是的,我不认为这是正确的做法。您将获得一个被命令订购的集合,但实际上并非如此。

答案 2 :(得分:-1)

底线,IOrderedEnumerable仅用于为OrderBy()/ ThenBy()方法提供语法结构,从而阻止您尝试使用ThenBy()启动排序子句。处理。它不是一个“标记”,它将集合标识为有序,除非它实际上是由OrderBy()订购的。因此,答案是如果排序方法为null应该表明可枚举是某种“默认顺序”,则应指定默认顺序(如当前实现所做的那样)。如果没有指定一个SortingMethod,你推断它是“无序排序”并且不关心实际的顺序,那就说明可枚举是有序的,而实际上并非如此。

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

Servy的回答,说明OrderBy(x =&gt; 1)是一个noop而且应该从SQL提供程序中进行优化,忽略了这个针对Enumerable的调用仍会做的事实一些工作,并且大多数SQL提供商实际上优化这种类型的调用;在大多数Linq提供程序中,OrderBy(x =&gt; 1)将生成一个带有“ORDER BY 1”子句的查询,该子句不仅强制SQL提供程序执行自己的排序,它实际上会导致更改顺序,因为在T-SQL中,至少“ORDER BY 1”表示按选择列表的第一列排序。