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.
为什么不摆脱冗余:
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)
从可枚举中可以提取的信息并不多,因此也许最好将查询转换为 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)