LINQ .Any和.Exists - 有什么区别?

489
使用LINQ在集合上,以下代码有什么区别?
if(!coll.Any(i => i.Value))

并且

if(!coll.Exists(i => i.Value))

当我拆解.Exists时,看起来好像没有代码。为什么会这样呢?

10
你编译的代码长什么样?你是如何反汇编的?使用了ildasm吗?你原本期望找到什么,但实际上没有找到? - Meinersbur
6个回答

482

List.Exists(对象方法 - MSDN)

确定 List(T) 是否包含与指定谓词定义的条件匹配的元素。

这个方法自 .NET 2.0 就已经存在了,所以在 LINQ 之前。它的设计是用于 Predicate 委托,但是 lambda 表达式也是向后兼容的。而且,只有 List 类才有这个方法(甚至连 IList 都没有)。


IEnumerable.Any(扩展方法 - MSDN)

确定序列中是否有任何元素满足条件。

这是在.NET 3.5中新增的,它使用Func(TSource, bool)作为参数,因此旨在与lambda表达式和LINQ一起使用。

在行为方面,它们是相同的。


9
我后来在另一个帖子中发了一篇文章,列举出了所有Linq与.NET 2的List<>实例方法“相对应”的内容。 - Jeppe Stig Nielsen
4
这里有些答案说 any() 比 exists() 慢。但在实际应用中,你通常可以将它与其他 Linq 方法结合使用,这样会使它更快,例如 myIEnum.Where(a => String.Equals(a.sex, "male")).Any(a => String.Equals(a.name, "Joe"))。你不能像这样在 exists() 中使用 where()。 - volkit

227
区别在于AnySystem.Linq.Enumerable中为任何IEnumerable<T>定义的扩展方法。它可以用于任何IEnumerable<T>实例。 Exists似乎不是一个扩展方法。我猜测你的例子中col1的类型是List<T>。如果是这样,Exists是一个实例方法,其功能与Any非常相似。
简而言之,这两个方法本质上是相同的。一个比另一个更通用。
  • Any还有一个不带参数的重载,只是查找可枚举对象中的任何项。
  • Exists没有这样的重载。

17
说得好(+1)。List<T>.Exists 自 .Net 2 就存在了,但只适用于泛型列表。IEnumerable<T>.Any 是在 .Net 3 中新增的扩展方法,可用于任何可枚举集合。还有类似的成员,如 List<T>.Count(一个属性)和 IEnumerable<T>.Count()(一个方法)。 - Keith

76

简而言之; 在性能方面,Any 看起来似乎更慢(如果我已经正确设置了以几乎相同的时间评估两个值)

        var list1 = Generate(1000000);
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s +=" Any: " +end1.Subtract(start1);
            }

            if (!s.Contains("sdfsd"))
            {

            }

测试列表生成器:

private List<string> Generate(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            list.Add( new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    new RNGCryptoServiceProvider().GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray())); 
        }

        return list;
    }

有1000万条记录

"任何: 00:00:00.3770377 存在: 00:00:00.2490249"

有500万条记录

"任何: 00:00:00.0940094 存在: 00:00:00.1420142"

有100万条记录

"任何: 00:00:00.0180018 存在: 00:00:00.0090009"

有50万条记录,(我还改变了它们被评估的顺序,以查看是否与首先运行的任何一个相关的附加操作。)

"存在: 00:00:00.0050005 任何: 00:00:00.0100010"

有10万条记录

"存在: 00:00:00.0010001 任何: 00:00:00.0020002"

似乎Any要慢大约两倍。

编辑:对于500万和1000万条记录,我改变了生成列表的方式,Exists突然比Any慢,这意味着我测试的方式存在问题。

新的测试机制:

private static IEnumerable<string> Generate(int count)
    {
        var cripto = new RNGCryptoServiceProvider();
        Func<string> getString = () => new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    cripto.GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray());

        var list = new ConcurrentBag<string>();
        var x = Parallel.For(0, count, o => list.Add(getString()));
        return list;
    }

    private static void Test()
    {
        var list = Generate(10000000);
        var list1 = list.ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {

            }
        }

编辑2:好的,为了消除生成测试数据的影响,我将所有数据都写入文件中,现在从那里读取。

 private static void Test()
    {
        var list1 = File.ReadAllLines("test.txt").Take(500000).ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {
            }
        }
    }

10兆字节

"任意: 00:00:00.1640164 存在: 00:00:00.0750075"

5兆字节

"任意: 00:00:00.0810081 存在: 00:00:00.0360036"

1兆字节

"任意: 00:00:00.0190019 存在: 00:00:00.0070007"

500千字节

"任意: 00:00:00.0120012 存在: 00:00:00.0040004"

输入图像描述


4
请注意,这里并不是贬低你的意思,但我对这些基准测试结果持怀疑态度。看看这些数字:每个结果都发生了递归 (3770377 : 2490249)。至少对我来说,这是某些不正确的迹象。我对这里的数学并不百分之百确定,但我认为该循环出现的几率每个值为 1/999^999 (或者是 999! ?)。因此,连续发生8次的概率是微乎其微的。我想这是因为您在基准测试中使用了DateTime - Jerri Kangasniemi
3
@JerriKangasniemi,我能建议您修复它并发布一个答案吗? - Matas Vaitkevicius
1
如果我正确理解你的结果,你报告的Any仅比Exists快2到3倍。我不明白数据如何甚至稍微支持你的断言“似乎Any要慢2个数量级”。它确实慢一点,但不是数量级的差距。 - Suncat2000
最好删除中间/错误的结果(在开始阅读的顶部)。结尾再次非常有趣。甚至有漂亮的图表。谢谢! - Andreas Reiff
由于Any()是一个接口调用,所以它的速度较慢是可以预料的。 - Tarec
显示剩余4条评论

23

延续Matas的答案,对基准测试进行探讨。

简而言之:Exists()和Any()的速度相同。

首先:使用Stopwatch进行基准测试不是十分精确(请参见series0ne在另一个但类似主题上的回答),但它比DateTime更加精确。

获取真正精确的读数的方法是使用性能分析。但是,衡量两种方法性能的一种方式是执行这两种方法大量次,然后比较每个方法的最快执行时间。这样,JIT编译和其他噪声会给我们带来错误的读数(它确实会这样做),因为在某种意义上,两个执行都是“同样误导人的”。

static void Main(string[] args)
    {
        Console.WriteLine("Generating list...");
        List<string> list = GenerateTestList(1000000);
        var s = string.Empty;

        Stopwatch sw;
        Stopwatch sw2;
        List<long> existsTimes = new List<long>();
        List<long> anyTimes = new List<long>();

        Console.WriteLine("Executing...");
        for (int j = 0; j < 1000; j++)
        {
            sw = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw.Stop();
                existsTimes.Add(sw.ElapsedTicks);
            }
        }

        for (int j = 0; j < 1000; j++)
        {
            sw2 = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw2.Stop();
                anyTimes.Add(sw2.ElapsedTicks);
            }
        }

        long existsFastest = existsTimes.Min();
        long anyFastest = anyTimes.Min();

        Console.WriteLine(string.Format("Fastest Exists() execution: {0} ticks\nFastest Any() execution: {1} ticks", existsFastest.ToString(), anyFastest.ToString()));
        Console.WriteLine("Benchmark finished. Press any key.");
        Console.ReadKey();
    }

    public static List<string> GenerateTestList(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            Random r = new Random();
            int it = r.Next(0, 100);
            list.Add(new string('s', it));
        }
        return list;
    }

执行上述代码4次后(每次对一个包含1,000,000个元素的列表执行1,000个Exists()Any()操作),很容易看出这两种方法的速度几乎相同。

Fastest Exists() execution: 57881 ticks
Fastest Any() execution: 58272 ticks

Fastest Exists() execution: 58133 ticks
Fastest Any() execution: 58063 ticks

Fastest Exists() execution: 58482 ticks
Fastest Any() execution: 58982 ticks

Fastest Exists() execution: 57121 ticks
Fastest Any() execution: 57317 ticks

存在一些微小的差异,但这种差异太小了,不能归因于背景噪声。我猜测如果进行10000或100000次 Exists()Any(),那么这种微小的差异将会更多或少消失。


我可以建议您分别进行10,000、100,000和1,000,000的测试,以便在方法上更加系统化。另外,为什么要使用最小值而不是平均值呢? - Matas Vaitkevicius
2
最小值是因为我想比较每种方法的最快执行(可能是背景噪音最少的)。虽然我可能会用更多迭代来做到这一点,但这将是后来的事情(我怀疑我的老板想付钱让我这样做,而不是处理我们的待办事项)。 - Jerri Kangasniemi
14
如果你发布的代码是你真正执行过的,那么得到类似的结果并不奇怪,因为你在两个测量中都调用了Exists方法。 ;) (注:最后的表情符号“;)”表示玩笑或调侃的意思) - Simon Touchtech
嘿,是的,现在你这么说我也看到了。不过这并没有在我的执行中发生。这只是一个我比较的简化概念。:P - Jerri Kangasniemi
6
Random r = new Random(); 真的需要从那个 for 循环中拿出来。 - Jesse C. Slicer

8

当你修正测量值 - 如上所述:任何和存在,并添加平均值 - 我们将得到以下输出:

Executing search Exists() 1000 times ... 
Average Exists(): 35566,023
Fastest Exists() execution: 32226 

Executing search Any() 1000 times ... 
Average Any(): 58852,435
Fastest Any() execution: 52269 ticks

Benchmark finished. Press any key.

5

另外,这只适用于值为bool类型的情况。通常与谓词一起使用。任何谓词都可以用来判断是否有任何元素能满足给定条件。在此,您只需将元素 i 映射到一个布尔属性即可。它会搜索 Value 属性为 true 的“i”。完成后,该方法将返回 true。


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