使用Any()时优化OrderBy()

Fow*_*owl 6 linq optimization linq-to-objects

所以我有一个相当标准的LINQ-to-Object设置.

var query = expensiveSrc.Where(x=> x.HasFoo)
                        .OrderBy(y => y.Bar.Count())
                        .Select(z => z.FrobberName);    

// ...

if (!condition && !query.Any())
 return; // seems to enumerate and sort entire enumerable 

// ...

foreach (var item in query)
   // ...
Run Code Online (Sandbox Code Playgroud)

这列举了两次.这很糟糕.

var queryFiltered = expensiveSrc.Where(x=> x.HasFoo);

var query = queryFiltered.OrderBy(y => y.Bar.Count())
                         .Select(z => z.FrobberName); 

if (!condition && !queryFiltered.Any())
   return;

// ...

foreach (var item in query)
   // ...
Run Code Online (Sandbox Code Playgroud)

工作,但有更好的方法吗?

是否有任何非疯狂的方式来"启发"Any()绕过非必需的操作?我想我记得这种优化进入了EduLinq.

Ani*_*Ani 9

为什么不摆脱冗余:

if (!query.Any())
 return; 
Run Code Online (Sandbox Code Playgroud)

它实际上似乎没有任何目的 - 即使没有它,foreach如果查询没有产生任何结果,则不会执行.因此,通过Any()签入,您可以在快速路径中保存任何内容,并在慢速路径中枚举两次.

另一方面,如果你必须知道在循环结束是否有任何结果,你也可以使用一个标志:

bool itemFound = false;

foreach (var item in query)
{
    itemFound = true;
    ... // Rest of the loop body goes here.
}

if(itemFound)
{
   // ...
}
Run Code Online (Sandbox Code Playgroud)

或者如果你真的担心循环体中的冗余标志设置,你可以直接使用枚举器:

using(var erator = query.GetEnumerator())
{
    bool itemFound = erator.MoveNext();

    if(itemFound)
    {
       do
       {
           // Do something with erator.Current;
       } while(erator.MoveNext())
    }

   // Do something with itemFound
}
Run Code Online (Sandbox Code Playgroud)


use*_*188 2

从可枚举中可以提取的信息并不多,因此也许最好将查询转换为 IQueryable?此Any扩展方法沿着其表达式树遍历,跳过所有不相关的操作,然后将重要的分支转换为委托,可以调用该委托来获取优化的 IQueryable。Any显式应用标准方法以避免递归。不确定极端情况,也许缓存已编译的查询是有意义的,但对于像您这样的简单查询,它似乎可以工作。

static class QueryableHelper {
    public static bool Any<T>(this IQueryable<T> source) {
        var e = source.Expression;
        while (e is MethodCallExpression) {
            var mce = e as MethodCallExpression;
            switch (mce.Method.Name) {
                case "Select":
                case "OrderBy":
                case "ThenBy": break;
                default: goto dun;
            }
            e = mce.Arguments.First();
        }
        dun:
        var d = Expression.Lambda<Func<IQueryable<T>>>(e).Compile();
        return Queryable.Any(d());
    }
}
Run Code Online (Sandbox Code Playgroud)

查询本身必须像这样修改:

var query = expensiveSrc.AsQueryable()
                        .Where(x=> x.HasFoo)
                        .OrderBy(y => y.Bar.Count())
                        .Select(z => z.FrobberName); 
Run Code Online (Sandbox Code Playgroud)