Are Where(condition).Any() and Any(condition) equivalent Where(condition).Any()和Any(condition)是否等效

5

我看到的一半使用Any方法的Linq查询示例是通过将其应用于Where()调用的结果来实现的,另一半则直接应用于集合。这两种风格是否总是等效的,还是存在可能返回不同结果的情况?

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

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

5
这关系到两个重要问题:
  • 这是标准的“Where” / “Any”(例如Enumerable。*或Queryable。*)还是自定义的?(如果是后者,则一切皆为未知数)
  • 如果它是Queryable.*,那么提供程序是什么?
后者非常重要。例如,LINQ-to-SQL和LINQ-to-EF在Single方面的行为不同,因此我不能假设它们对于Any的行为相同。更加深奥的提供程序可以做任何事情。但更多:LINQ-to-SQL对于Single(predicate)与Where(predicate).Single(以及First)执行不同操作(有关identity-manager)。事实上,根据3.5、3.5SP1或4.0,在那里有3种不同的可用行为。
此外,如记得正确,LINQ-to-ADO.NET-Data-Services对EF具有不同(相反,从内存中记忆)的支持 - 因此(再次从内存中记起),当其中一个提供程序仅支持Single(predicate) 时,另一个只支持Where(predicate).Single(); 可以很容易地推断出Any()可能会受到不同提供程序的影响。
因此:虽然predicate下的Any和Where(predicate).Any()在语义上等效,但是如果没有极其详细的上下文信息,就无法确定它们是否实际上是相同的。

它们甚至可能不是语义上等价的,例如如果Where具有副作用。这进入了你的第二个要点。 - Chris Shain
在这种情况下,我们明确谈论的是IEnumerable,因为原文中使用了List。 - Chris Shain
@Chris 我的理解是,这个列表只是测试 - 在这种情况下 足以表明它可以表现得相同,但并不能证明一般情况。 - Marc Gravell
你能提供一下Linq-to-SQL/EF表现不同的参考资料吗?我正在支持使用它们的应用程序,并希望在遇到任何微妙的问题之前了解它们。 - Dan Is Fiddling By Firelight
@Dan 如果我没记错的话,在EF < 4.0中,Single()根本无法使用 - 它会抛出NotSupportedException()或类似的异常。另一个例子是 Take() - 在LINQ-to-SQL中,你可以在无序的序列上使用Take(),它将使用底层数据库的顺序(即没有ORDER BY) - 然而,EF拒绝执行Take()除非该序列已经排序。 - Marc Gravell

4

从逻辑上讲,两者没有区别,但就性能而言,后者更优:

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

比这更高效:
stuff.Where(m => m.parameter == 1).Any();

因为前者并没有使用迭代器(yield return)来生成它的结果。而Where()子句则使用了迭代器,迭代器虽好,但也增加了额外的处理开销。

这样的开销非常小,但通常我会选择最简洁易读的表达式,以提高性能和可维护性。


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

延迟执行方面如何体现? - Gabe
@gabe - 因为where使用yield运算符并返回IEnumerable<T> - Daniel A. White
@gabe:是的,这会创建一个“迭代器”,它是一个状态机,用于“记住”方法中的位置(粗略地说)。任何一个简单的foreach循环,因此它的性能更好。 - James Michael Hare

2

这里是一段 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会导致额外的枚举,从而增加开销。

0

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


也许这应该是另一个问题,但一个对象如何提供自己的Where方法? - smoak
框架中是否有任何对象可以做到这一点;或者只有在处理“聪明”的同事或库作者编写的类时才需要关注这个问题。 - Dan Is Fiddling By Firelight
@smoak 通过拥有一个适当签名的 Where 方法即可,这就是所需的全部,即使是类似 SQL 的 LINQ 语法。 - Marc Gravell

0

有微妙的差别。

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

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

然而,我想不到这种差异会如何影响执行,因为在枚举可枚举对象之前不会调用 Any

如果另一个线程在运行 where 部分和 any 部分之间更改了可枚举对象,则会抛出异常,因此不会更改结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接