我应该在LINQ查询中使用两个"where"子句或"&&"吗?

Ðаn*_*Ðаn 70 .net c# linq

在编写具有多个"和"条件的LINQ查询时,我应该编写where包含&&多个where子句的单个子句,每个条件一个吗?

static void Main(string[] args)
{
    var ints = new List<int>(Enumerable.Range(-10, 20));

    var positiveEvensA = from i in ints
                         where (i > 0) && ((i % 2) == 0)
                         select i;

    var positiveEvensB = from i in ints
                         where i > 0
                         where (i % 2) == 0
                         select i;

    System.Diagnostics.Debug.Assert(positiveEvensA.Count() == 
                                         positiveEvensB.Count());
}
Run Code Online (Sandbox Code Playgroud)

是否有任何差别比之间的个人喜好或编码风格(排长队,可读性等)等positiveEvensApositiveEvensB

想到的一个可能的区别是,不同的LINQ提供者可能能够更好地处理多个wheres而不是更复杂的表达; 这是真的?

Ree*_*sey 48

我个人总是会使用&&与两个where子句一起使用,只要它不会使语句难以理解.

在你的情况下,它可能不会是都明显可言,但有2 where子句肯定会影响性能,如果你有一个大集合,如果你用所有的结果从该查询.例如,如果在结果上调用.Count(),或者遍历整个列表,则会运行第一个where子句,创建一个将再次完全枚举的新IEnumerable,并使用第二个委托.

将2个子句链接在一起会导致查询形成单个委托,该委托在枚举集合时运行.这导致通过集合进行一次枚举,并在每次返回结果时调用一次委托.

如果你拆分它们,情况会发生变化.当你的第一个where子句枚举原始集合时,第二个where子句枚举它的结果.这可能(最坏的情况)导致通过集合的2个完整枚举和每个成员调用2个委托,这可能意味着此声明(理论上)可能需要2倍的运行时速度.

如果您决定使用2 where子句,则首先放置更具限制性的子句将有很大帮助,因为第二个where子句仅在传递第一个子句的元素上运行.

现在,在你的情况下,这没关系.在大型系列上,它可以.作为一般经验法则,我会去:

1)可读性和可维护性

2)表现

在这种情况下,我认为这两个选项都是同样可维护的,所以我会选择性能更高的选项.

  • 使用2"where"子句,这仍然只是一个迭代,每个项目有2个委托调用 - 而不是声称的2次迭代. (34认同)
  • @CodesInChaos:是的[它有](https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,e11d6842eee7c2e6)(查看“CombinePredicates”)。它基本上是将多个 where 扁平化为具有多个“&amp;&amp;”的单个。 (6认同)
  • Marc,当我说两次迭代时,我应该更明确.使用2 where子句,您可以构造两个单独的枚举器 - 第一个创建一个由第二个枚举的枚举器.但这很小 - 它没有做2个循环,只是使用单独的枚举器进行枚举. (4认同)
  • 我认为`Where`对multiple where子句有一些特殊的优化.由于多个委托调用,它仍然会变慢. (3认同)

Jar*_*Par 20

这主要是个人风格问题.就个人而言,只要该where条款适用于一行,我就对这些条款进行分组.

使用多个wheres往往性能较差,因为它需要对每个元素进行额外的委托调用才能实现这一目标.然而,它可能是一个微不足道的问题,只有在剖析器显示它是一个问题时才应该考虑.


Tim*_*ter 15

正如 Jared Par 所说:这取决于您的个人喜好、可读性和用例。例如,如果您的方法有一些可选参数,并且您想要过滤给定的集合,那么这Where是完美的:

IEnumerable<SomeClass> matchingItems = allItems;
if(!string.IsNullOrWhiteSpace(name))
    matchingItems = matchingItems
       .Where(c => c.Name == name);
if(date.HasValue)
    matchingItems = matchingItems
       .Where(c => c.Date == date.Value);
if(typeId.HasValue)
    matchingItems = matchingItems
       .Where(c => c.TypeId == typeId.Value);
return matchingItems;
Run Code Online (Sandbox Code Playgroud)

如果你想这样做&&,那就玩得开心;)

我不同意JaredReed 的Where观点是多个应该存在的性能问题。实际上,它以一种将多个谓词组合为一个谓词的方式进行了优化,如您在此处(在)Where中看到的那样。CombinePredicates

但我想知道如果集合很大并且有多个都Where必须进行评估,它是否真的没有太大影响。令我惊讶的是,以下基准测试表明,即使是多种Where方法也稍微更有效。摘要:

方法 意思是 错误 标准差
多重地点 1.555秒 0.0310秒 0.0392秒
多重与 1.571秒 0.0308秒 0.0649秒

这是基准代码,我认为对于这个测试来说已经足够了:

#LINQPad optimize+

void Main()
{
    var summary = BenchmarkRunner.Run<WhereBenchmark>();
}


public class WhereBenchmark
{
    string[] fruits = new string[] { "apple", "mango", "papaya", "banana", "guava", "pineapple" };
    private IList<string> longFruitList;
    
    [GlobalSetup]
    public void Setup()
    {
        Random rnd = new Random();
        int size = 1_000_000;
        longFruitList = new List<string>(size);
        for (int i = 1; i < size; i++)
            longFruitList.Add(GetRandomFruit());

        string GetRandomFruit()
        {
            return fruits[rnd.Next(0, fruits.Length)];
        }
    }


    [Benchmark]
    public void MultipleWhere()
    {
        int count = longFruitList
            .Where(f => f.EndsWith("le"))
            .Where(f => f.Contains("app"))
            .Where(f => f.StartsWith("pine"))
            .Count(); // counting pineapples
    }
    
    [Benchmark]
    public void MultipleAnd()
    {
        int count = longFruitList
            .Where(f => f.EndsWith("le") && f.Contains("app") && f.StartsWith("pine"))
            .Count(); // counting pineapples
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 6

性能问题仅适用于基于内存的集合。Linq to SQL生成延迟执行的表达式树。此处有更多详细信息:

具有LINQ扩展方法的多个WHERE子句