【问题标题】:Should I use two "where" clauses or "&&" in my LINQ query?我应该在 LINQ 查询中使用两个“where”子句还是“&&”?
【发布时间】:2010-10-14 11:04:51
【问题描述】:

在编写具有多个“and”条件的 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());
}

positiveEvensApositiveEvensB 之间除了个人偏好或编码风格(长线、可读性等)之外还有什么不同吗?

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

【问题讨论】:

    标签: c# .net linq


    【解决方案1】:

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

    在您的情况下,它可能根本不会引起注意,但是如果您有一个大型集合,并且如果您使用此查询的所有结果,那么拥有 2 个 where 子句肯定会对性能产生影响。例如,如果您对结果调用 .Count() 或遍历整个列表,则第一个 where 子句将运行,创建一个新的 IEnumerable,它将再次被完全枚举,并带有第二个委托。

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

    如果你把它们分开,事情就会改变。当您的第一个 where 子句枚举原始集合时,第二个 where 子句枚举其结果。这可能会导致(最坏的情况)您的集合中的 2 个完整枚举和每个成员调用的 2 个委托,这可能意味着该语句(理论上)可能需要 2 倍的运行时速度。

    如果您决定使用 2 个 where 子句,首先放置限制性更强的子句会有很大帮助,因为第二个 where 子句仅在通过第一个 where 子句的元素上运行。

    现在,就您而言,这无关紧要。在大型收藏中,它可以。作为一般经验法则,我选择:

    1. 可读性和可维护性

    2. 性能

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

    【讨论】:

    • 使用 2 个“where”子句,这仍然只是一个迭代,每个项目有 2 个委托调用 - 而不是声称的 2 个迭代。
    • 马克,当我说两次迭代时,我应该更明确一点。使用 2 个 where 子句,您可以构造两个单独的枚举器 - 第一个 where 创建一个由第二个枚举器枚举的枚举器。但这很小——它不做 2 个循环,只是用单独的枚举器进行枚举。
    • 我认为Where 对多个 where 子句的情况进行了一些特殊的优化。由于多次委托调用,它仍然会变慢。
    • @CodesInChaos:是的it has(看看CombinePredicates)。它基本上是将多个 where 与多个 &amp;&amp; 一起“压平”。
    【解决方案2】:

    这主要是个人风格问题。就个人而言,只要where 子句适合一行,我就会对子句进行分组。

    使用多个wheres 往往会降低性能,因为它需要对每个元素进行额外的委托调用才能达到这种效果。然而,这可能是一个无关紧要的问题,只有在分析器显示它是一个问题时才应考虑。

    【讨论】:

      【解决方案3】:

      性能问题仅适用于基于内存的集合... Linq to SQL 生成延迟执行的表达式树。更多细节在这里:

      Multiple WHERE Clauses with LINQ extension methods

      【讨论】:

        【解决方案4】:

        正如 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;
        

        如果你想用&amp;&amp; 来做这件事,玩得开心;)

        我不同意JaredReed 的地方是多个Where 应该有的性能问题。实际上,Where 进行了优化,将多个谓词组合为一个,如您所见here(在CombinePredicates 中)。

        但我想知道如果集合很大并且有多个Where 都必须进行评估,它是否真的没有太大影响。我很惊讶下面的benchmark 揭示了即使是多重Where 方法也稍微更有效。总结:

        Method Mean Error StdDev
        MultipleWhere 1.555 s 0.0310 s 0.0392 s
        MultipleAnd 1.571 s 0.0308 s 0.0649 s

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

        #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
            }
        }
        

        【讨论】:

          【解决方案5】:

          就像其他人建议的那样,这更多是个人喜好。我喜欢 && 的使用,因为它更具可读性并且模仿了其他主流语言的语法。

          【讨论】:

            【解决方案6】:

            如果您运行 SQL Profiler 并检查生成的查询,您可以看到两种查询在性能方面没有区别。 所以,只是你对代码风格的品味。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-08-14
              • 1970-01-01
              • 1970-01-01
              • 2020-12-31
              • 1970-01-01
              相关资源
              最近更新 更多