Where(condition).Any()和Any(condition)是等价的

本文关键字:Any condition Where | 更新日期: 2023-09-27 18:09:26

在我看到的使用Any方法的Linq查询示例中,大约有一半是通过将其应用于Where()调用的结果来实现的,另一半则直接将其应用于集合。这两种样式是否总是相等的,或者是否存在它们可能返回不同结果的情况?

我的测试支持前者的结论;但边缘情况并不总是那么容易找到。

List<MyClass> stuff = GetStuff();
bool found1 = stuff.Where(m => m.parameter == 1).Any();
bool found2 = stuff.Any(m => m.parameter == 1);

Where(condition).Any()和Any(condition)是等价的

这可以归结为两个重要的问题:

  • 是标准的"Where"/"Any"(如Enumerable)吗?*或可查询的。*),还是自定义?(如果是后者,所有的赌注都是无效的)
  • 如果它是可查询的。*,提供者是什么?

后者非常重要。例如,LINQ-to-SQL和LINQ-to-EF在Single下的行为不同,所以我不会假设对于Any它们的行为相同。一个更深奥的提供者可以做任何事情。但更多的是:LINQ-to-SQL对Single(谓词)和Where(谓词)做不同的事情(身份管理器)。单身(也是第一次)。实际上,LINQ-to-SQL中有3种不同的行为,这取决于3.5、3.5 sp1或4.0。

此外,IIRC LINQ-to-ADO。NET-Data-Services具有不同的支持EF - so(同样来自内存),而一个提供者只支持Single(谓词),另一个只支持Where(谓词).Single();建议Any()可能受到不同提供者的类似影响并不是一个大的飞跃。

所以:虽然Any(predicate)和Where(predicate).Any()在语义上是等价的,但是如果没有非常详细的上下文的信息,就不可能说它们实际上是相同的

逻辑上没有区别,但性能方面,后者:

stuff.Any(m => m.parameter == 1);

性能更好。
stuff.Where(m => m.parameter == 1).Any();

,因为前者不使用迭代器(yield return)来生成结果。Where()子句可以,迭代器很好,但它们确实增加了额外的处理开销。

它很大吗?不,但通常情况下,为了性能和可维护性,我会选择最简洁易读的表达式。

使用标准的LINQ函数,没有区别,因为where是延迟的操作——只是一个额外的函数调用。

下面是一小段c#代码:

class Program
{
    List<string> data = new List<string>(){ "ABC", "DEF", "H" };
    static void Main(string[] args)
    {
        var p = new Program();
    }

    private Program()
    {
        UseWhereAndAny();
        UseAny();
    }

    private void UseWhereAndAny()
    {
        var moreThan2 = data.Where(m => m.Length > 2).Any();
    }
    private void UseAny()
    {
        var moreThan2 = data.Any(m => m.Length > 2);
    }
}

如果你检查IL代码,你会发现两者之间有一点不同:

    .method private hidebysig 
    instance void UseAny () cil managed 
{
    // Method begins at RVA 0x2134
    // Code size 45 (0x2d)
    .maxstack 4
    .locals init (
        [0] bool moreThan2
    )
    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldfld class [mscorlib]System.Collections.Generic.List`1<string> AnyWhere.Program::data
    IL_0007: ldsfld class [mscorlib]System.Func`2<string, bool> AnyWhere.Program::'CS$<>9__CachedAnonymousMethodDelegate4'
    IL_000c: brtrue.s IL_0021
    IL_000e: ldnull
    IL_000f: ldftn bool AnyWhere.Program::'<UseAny>b__3'(string)
    IL_0015: newobj instance void class [mscorlib]System.Func`2<string, bool>::.ctor(object, native int)
    IL_001a: stsfld class [mscorlib]System.Func`2<string, bool> AnyWhere.Program::'CS$<>9__CachedAnonymousMethodDelegate4'
    IL_001f: br.s IL_0021
    IL_0021: ldsfld class [mscorlib]System.Func`2<string, bool> AnyWhere.Program::'CS$<>9__CachedAnonymousMethodDelegate4'
    IL_0026: call bool [System.Core]System.Linq.Enumerable::Any<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, bool>)
    IL_002b: stloc.0
    IL_002c: ret
} // end of method Program::UseAny

而UserWhere方法是:

 .method private hidebysig 
    instance void UseWhereAndAny () cil managed 
{
    // Method begins at RVA 0x20d8
    // Code size 50 (0x32)
    .maxstack 4
    .locals init (
        [0] bool moreThan2
    )
    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldfld class [mscorlib]System.Collections.Generic.List`1<string> AnyWhere.Program::data
    IL_0007: ldsfld class [mscorlib]System.Func`2<string, bool> AnyWhere.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
    IL_000c: brtrue.s IL_0021
    IL_000e: ldnull
    IL_000f: ldftn bool AnyWhere.Program::'<UseWhereAndAny>b__1'(string)
    IL_0015: newobj instance void class [mscorlib]System.Func`2<string, bool>::.ctor(object, native int)
    IL_001a: stsfld class [mscorlib]System.Func`2<string, bool> AnyWhere.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
    IL_001f: br.s IL_0021
    IL_0021: ldsfld class [mscorlib]System.Func`2<string, bool> AnyWhere.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
    IL_0026: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, bool>)
    IL_002b: call bool [System.Core]System.Linq.Enumerable::Any<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
    IL_0030: stloc.0
    IL_0031: ret
} // end of method Program::UseWhereAndAny

据我所知,使用Where和Any会导致额外的枚举带来更多的开销。

虽然在大多数情况下它们在语义上是等价的,但是有可能为对象提供自己的 Where方法,这可能导致stuff.Where(foo).Any()stuff.Any(foo)非常不同。

有一个微妙的区别。

调用Any()检查可枚举对象是否为空,如果为空则返回false。

调用Any(Func<TSource, bool> predicate)检查可枚举对象中是否有匹配谓词的项,如果不匹配则返回false。

然而,我不认为这种差异会影响执行,因为Any在枚举对象被枚举之前不会被调用。

如果另一个线程改变了where部分和任何部分之间的可枚举数,将会抛出异常,因此不会改变结果。