【问题标题】:C#, filtering IEnumerable with multiple .Where() statements, performance hitsC#,使用多个 .Where() 语句过滤 IEnumerable,性能下降
【发布时间】:2016-08-19 18:55:19
【问题描述】:

我有一个 IEnumerable,我正在通过 .Where 表达式对其应用多个过滤器。我的代码看起来像这样

public List<MyObject> FilteringFunction(List<MyObject> listToFilter, List<Filter> filters)
{
    // A dirty way to have an enumerable instead of List
    var enumerableToFilter = listToFilter.Where(x => true);

    foeach(var filter in filters)
    {
        enumerableToFilter = enumerableToFilter.Where(x => x.Value.Contains(filter.Value));
    }

    return enumerableToFilter.ToList();

}

我是否只会遍历我的收藏集一次? (因为我只有一个使用 LINQ to SQL 的数据库调用)

【问题讨论】:

  • 您的代码没有多大意义...如果您有不同值的过滤器,过滤后的结果将始终为空
  • @ThomasLevesque 除非你已经覆盖了== 使得不相等的对象返回true。 (并不是说这样做是明智的。)
  • @ThomasLevesque,对不起,我正在简化实际代码以使其更具可读性并过度简化。实际的 where 语句并不简单 == ,而是更复杂。例如,我将纠正我的问题并将其设为 .Contains()。
  • @ThomasLevesque 在实际代码中有不同种类的过滤器(等于、包含、startwith、更大、更小等)应用于 MyObject 的不同字段
  • 顺便说一句,将列表转换为 IEnumerable 的一种不太脏的方法是使用 .AsEnumerable() 扩展方法。

标签: c# performance linq linq-to-entities ienumerable


【解决方案1】:

Enumerables 会延迟执行,直到您对其进行迭代,并且多个过滤器将应用于通过集合的一次迭代。与其他 linq 语句结合可能会强制进行早期枚举,我还没有测试每个组合。这只会在非常大的数据集或低规格性能关键系统上出现问题。

这是一个使用 Visual Studios c# 交互的示例

> class Item
. {
.     private int _number;
.     public int Number
.     {
.         get { Console.WriteLine($"Got number {_number}"); return _number; }
.         set { _number = value; }
.     }
. }
> 
> IEnumerable<Item> items = new List<Item>() { 
.     new Item { Number = 1 },
.     new Item { Number = 2 },
.     new Item { Number = 3 },
.     new Item { Number = 4 },
.     new Item { Number = 5 },
.     new Item { Number = 6 }
. };
> 
> var filteredItems = items.Where(item => item.Number > 3).Where(item => item.Number % 2 == 0);
> 
> var listedItems = filteredItems.ToList();
Got number 1
Got number 2
Got number 3
Got number 4
Got number 4
Got number 5
Got number 5
Got number 6
Got number 6
> 

请注意,1、2 和 3 被过滤掉了,并且没有对它们调用第二个过滤器方法。 4、5 和 6 都通过了第一个过滤器,因此两个过滤器都被应用。

关键点:请注意,在将可枚举读入列表之前,过滤实际上不会发生。在将结果枚举到列表之前,您将能够继续附加过滤器。

【讨论】:

  • 这是我正在寻找的信息。谢谢凯尔森鲍尔和@David L
【解决方案2】:

首先,结果肯定是空的,因为你用不同的值过滤相同的属性,假设你纠正了这个,回答你的问题,对于 iqueryable 它只会调用一次数据库,因为 Enumerable 对象是同样,因为迭代只会在您使用 foreach 或调用 GetEnumerator 迭代对象时发生,在您的情况下,您没有这样做

【讨论】:

    【解决方案3】:

    如果您有许多过滤器,则开销可能会变得很明显,这是由于许多委托创建和调用以及对Where 的许多调用。话虽如此,您可能应该运行性能测试以确定它是否真的是一个问题。

    您可以重构代码以测试同一谓词中的所有过滤器:

    public List<MyObject> FilteringFunction(List<MyObject> listToFilter, List<Filter> filters)
    {
        Func<MyObject, bool> predicate =
            x =>
                filters.All(f => x.Value.Contains(f.Value));
    
        return listToFilter.Where(predicate).ToList();
    }
    

    PS:关于这个:

    // A dirty way to have an enumerable instead of List
    

    有一种更清洁的方法:

    var enumerableToFilter = listToFilter as IEnumerable<MyObject>;
    

    甚至:

    var enumerableToFilter = listToFilter.AsEnumerable();
    

    【讨论】:

    • 谢谢,但是如果我的过滤器比简单的 .Contains 更复杂怎么办?我有多种过滤器(等于、包含、开始、更大、更小等)应用于 MyObject 的不同字段
    • 我不知道谁会对这么好的答案投反对票。
    • @Anarion,也许你应该重构你的代码,让过滤器有一个 bool Match(MyObject x) 抽象方法。这样,您可以根据过滤器的类型以不同的方式实现该方法,并使用调用站点的抽象。
    • @Anarion 是的(但过滤器将被枚举多少次)
    • 顺便说一句,由于延迟执行,即使您的原始代码也只迭代了一次集合。不过,我相信我建议的实现会更快,因为它的开销更少。但是您应该运行性能测试以确保。
    猜你喜欢
    • 2012-07-02
    • 1970-01-01
    • 2019-03-12
    • 2015-01-09
    • 2021-09-12
    • 1970-01-01
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    相关资源
    最近更新 更多