如果keySelector很慢,如何优化LINQ OrderBy?

时间:2012-07-11 10:26:19

标签: c# .net linq sorting sql-order-by

我想使用可能需要一些时间来计算的值来对对象列表进行排序。现在我有这样的代码:

public IEnumerable<Foo> SortFoo(IEnumerable<Foo> original)
{
    return foos.OrderByDescending(foo => CalculateBar(foo));
}

private int CalculateBar(Foo foo)
{
    //some slow process here
}

上面代码的问题在于它会为每个项目调用多次计算值,这是不好的。可能的优化是使用缓存值(可能是字典),但这意味着SortFoo必须在每次排序后清除缓存(以避免内存泄漏,我确实希望在每个{{1}上重新计算值呼叫)。

这个问题是否有更清洁,更优雅的解决方案?

2 个答案:

答案 0 :(得分:4)

由于每个项目在排序中多次与其他项目进行比较,因此您可以低成本地至少每个项目缓存一次计算。 如果您经常针对相同的值运行计算,则记住该函数将是您最好的选择,

public IEnumerable<Foo> SortFoo(IEnumerable<Foo> original)
{
    return foos
        .Select(f => new { Foo = f, SortBy = CalculateBar(f) })
        .OrderByDescending(f=> f.SortBy)
        .Select(f => f.Foo);
}

这会将计算减少到每个项目一次

答案 1 :(得分:4)

.OrderBy()似乎已针对慢keySelector进行了优化。

根据以下内容,.OrderBy()似乎会缓存您提供的keySelector代表的结果。

var random = new Random(0);
var ordered = Enumerable
    .Range(0, 10)
    .OrderBy(x => {
        var result = random.Next(20);
        Console.WriteLine("keySelector({0}) => {1}", x, result);
        return result;
    });
Console.WriteLine(String.Join(", ", ordered));

这是输出:

keySelector(0) => 14
keySelector(1) => 16
keySelector(2) => 15
keySelector(3) => 11
keySelector(4) => 4
keySelector(5) => 11
keySelector(6) => 18
keySelector(7) => 8
keySelector(8) => 19
keySelector(9) => 5
4, 9, 7, 3, 5, 0, 2, 1, 6, 8

如果每次比较都运行一次委托,我发现每个项目只能调用一次keySelector代表。