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


413

在集合上使用LINQ,以下代码行之间有什么区别?

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

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

更新1

当我反汇编时.Exists,似乎没有代码。

更新2

有人知道为什么那里没有代码吗?


9
您编译的代码看起来如何?您是如何拆卸的?ildasm?您期望找到什么但没有找到?
Meinersbur'2

Answers:


423

参阅文件

List.Exists(对象方法-MSDN

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

从.NET 2.0开始存在,因此在LINQ之前存在。打算与Predicate 委托一起使用,但是lambda表达式是向后兼容的。另外,只有List有这个(甚至没有IList)

IEnumerable.Any(扩展方法-MSDN)

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

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

在行为上,这些是相同的。


4
后来,我在另一个线程发表一篇文章,其中列出了.NET 2 List<>实例方法的所有Linq“等效项” 。
Jeppe Stig Nielsen

201

区别在于Any是IEnumerable<T>System.Linq.Enumerable上定义的任何内容的扩展方法。它可以在任何IEnumerable<T>实例上使用。

存在似乎不是扩展方法。我的猜测是coll是type List<T>。如果是,则Exists是一个实例函数,其功能与Any极为相似。

简而言之方法基本相同。一个比另一个更笼统。

  • Any也有一个不带参数的重载,只是在可枚举中查找任何项目。
  • 存在没有这种过载。

13
好吧(+1)。从.Net 2开始存在List <T> .Exists,但仅适用于通用列表。IEnumerable <T> .NET 3中添加了任何内容,作为可用于任何可枚举集合的扩展。也有类似的成员,例如List <T> .Count(它是一个属性)和IEnumerable <T> .Count()-一个方法。
基思

51

TLDR; 性能方面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”

拥有5M条记录

“任何:00:00:00.0940094存在:00:00:00.1420142”

拥有1M条记录

“任何:00:00:00.0180018存在:00:00:00.0090009”

使用500k时,(我还按顺序对它们进行了评估,以查看是否没有与哪个先运行有关的附加操作。)

“存在:00:00:00.0050005任意:00:00:00.0100010”

拥有10万条记录

“存在:00:00:00.0010001任意:00:00:00.0020002”

似乎Any要慢2个数量级。

编辑:对于5和10M记录,我更改了它生成列表的方式,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"))
            {

            }
        }

Edit2:好的,为了消除对生成测试数据的任何影响,我将其全部写入文件,然后从那里读取。

 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"))
            {
            }
        }
    }

10M

“任何:00:00:00.1640164存在:00:00:00.0750075”

5M

“任何:00:00:00.0810081存在:00:00:00.0360036”

1M

“任何:00:00:00.0190019存在:00:00:00.0070007”

50万

“任何:00:00:00.0120012存在:00:00:00.0040004”

在此处输入图片说明


3
不用丢脸,但我对这些基准表示怀疑。看一下数字:每个结果都有递归发生(3770377:2490249)。至少对我来说,这是不正确的肯定信号。我不确定这里的数学百分百,但是我认为这种重复模式发生的几率是每个值999 ^ 999(或999!也许是?)中的1。因此,连续发生8次的可能性很小。我认为这是因为您使用DateTime进行基准测试
Jerri Kangasniemi

@JerriKangasniemi孤立地重复相同的操作应该总是花费相同的时间,多次重复同样的事情。是什么让你说这是DateTime?
Matas Vaitkevicius

当然可以。问题仍然是,例如500k呼叫花费0120012秒是非常不可能的。而且,如果它是完全线性的,从而很好地解释了数字,那么1M呼叫将花费0240024秒(是两倍的时间),但是事实并非如此。1M呼叫比500k多花费58,(3)%,而10M比5M多花费102.5%。因此,它不是线性函数,因此对所有数字进行递归并不合理。我之所以提到DateTime是因为过去我自己遇到过问题,因为DateTime没有使用高精度计时器。
Jerri Kangasniemi

2
@JerriKangasniemi难道我建议你修复它,并张贴一个答案
麦塔斯Vaitkevicius

1
如果我正确地阅读了您的结果,则您报告的Any仅为现有速度的2至3倍。我看不出数据怎么能稍微支持您的断言,即“似乎将Any减慢2级”。当然,它要慢一些,而不是数量级。
Suncat2000

16

作为Matas关于基准测试答案的延续。

TL / DR:Exists()和Any()的速度相同。

首先,使用秒表进行基准测试并不精确(请参阅series0ne在另一个不同的主题上的答案),但是它比DateTime精确得多。

获得真正精确的读数的方法是使用性能概要分析。但是,了解两种方法的性能如何相互衡量的一种方法是执行两种方法的时间负荷,然后比较每种方法的最快执行时间。这样,JITing和其他噪声给我们带来不好的读数并没有关系(而且的确如此),因为在某种意义上,两个执行都“ 同样地误导了 ”。

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次Exists()Any()在包含1 000 000个元素的列表中),不难看出这些方法的速度几乎一样快。

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

这里一个细微的差别,但它是不是背景噪音来解释过小的差异。我的猜测是,如果一个会做10 000或100 000 Exists()Any()替代,这细微的差别就会消失或多或少。


我可以建议您做1万,10万和1000000,只是为了有条不紊地进行,还有为什么要选择最小值而不是平均值?
Matas Vaitkevicius

2
最小值是因为我想比较每种方法的最快执行速度(=最少的背景噪音)。我可能会进行更多次迭代,尽管会稍后进行(我怀疑我的老板想为此付费而不是通过积压工作)
Jerri Kangasniemi

我问过保罗·林德伯格(Paul Lindberg),他说没关系;)关于最低限度,我可以看到您的推理,但是更正统的方法是使用平均en.wikipedia.org/wiki/Algorithmic_efficiency#Practice
Matas Vaitkevicius

9
如果您发布的代码是您实际执行的代码,那么得到类似结果也就不足为奇了,因为您在这两个度量中都称呼Exists。;)
Simon Touchtech

嘿,是的,我也看到了,现在您说出来。虽然我没有执行。这只是作为我正在比较的内容的简化概念。:P
Jerri Kangasniemi '17

4

此外,这仅在Value为bool类型时有效。通常,它与谓词一起使用。通常会使用任何谓词来查找是否存在满足给定条件的元素。在这里,您只是在绘制从元素i到bool属性的映射。它将搜索Value属性为true的“ i”。完成后,该方法将返回true。


3

当您校正测量值时-如上所述:“任意和存在”,然后加上平均值-我们将获得以下输出:

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.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.