【问题标题】:LINQ .Any VS .Exists - What's the difference?LINQ .Any VS .Exists - 有什么区别?
【发布时间】:2010-10-27 03:15:02
【问题描述】:

在集合上使用LINQ,下面几行代码有什么区别?

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

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

更新 1

当我反汇编 .Exists 时,它看起来好像没有代码。

更新 2

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

【问题讨论】:

  • 你编译的代码是什么样子的?你是怎么拆机的?伊达斯姆?您希望找到什么但没有找到?

标签: c# linq collections


【解决方案1】:

查看文档

List.Exists(对象方法-MSDN)

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

这从 .NET 2.0 开始就存在,所以在 LINQ 之前。旨在与谓词 delegate 一起使用,但 lambda 表达式是向后兼容的。另外,只有 List 有这个(甚至没有 IList)

IEnumerable.Any(扩展方法-MSDN)

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

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

在行为上,它们是相同的。

【讨论】:

  • 我后来创建了a post in another thread,在其中我列出了 .NET 2 List<> 实例方法的所有 Linq“等效项”。
  • 这里的一些答案说 any() 比 exists() 慢。但在现实世界的用例中,您通常可以将其与其他 Linq 方法结合使用,这将使其更快,例如myIEnum.Where(a => String.Equals(a.sex, "male")).Any(a => String.Equals(a.name, "Joe"))。你不能像这样将 where() 与 exists() 一起使用。
【解决方案2】:

不同之处在于 Any 是 System.Linq.Enumerable 上定义的任何 IEnumerable<T> 的扩展方法。它可以用于任何IEnumerable<T> 实例。

Exists 似乎不是扩展方法。我的猜测是 coll 的类型为List<T>。 If so Exists 是一个实例方法,其功能与 Any 非常相似。

总之方法基本相同。一个比另一个更通用。

  • Any 也有一个重载,它不接受任何参数,只是在可枚举中查找任何项目。
  • Exists 没有这样的重载。

【讨论】:

  • 说得好(+1)。 List.Exists 自 .Net 2 以来一直存在,但仅适用于通用列表。 IEnumerable.Any 是在 .Net 3 中添加的,作为适用于任何可枚举集合的扩展。还有类似的成员,例如 List.Count,它是一个属性和 IEnumerable.Count() - 一个方法。
【解决方案3】:

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”

有 500 万条记录

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

拥有 100 万条记录

“任何: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”

500k

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

【讨论】:

  • 没有诋毁你,但我对这些基准持怀疑态度。看看数字:每个结果都有一个递归发生(3770377:2490249)。至少对我来说,这是一个确定的迹象,有些事情是不正确的。我不是百分百确定这里的数学,但我认为这种重复模式发生的几率是每个值 999^999(或 999!也许?)中的 1。所以它连续发生 8 次 的机会是无限小的。我想是因为你使用了DateTime for benchmarking
  • @JerriKangasniemi 单独重复相同的操作应该总是花费相同的时间,重复多次也是如此。是什么让你说它是 DateTime?
  • 当然可以。问题仍然是,例如 0120012 秒用于 500k 呼叫的可能性极小。如果它是完全线性的,从而很好地解释了这些数字,那么 1M 调用将花费 0240024 秒(两倍长),但事实并非如此。 1M 调用比 500k 长 58,(3)%,10M 比 5M 长 102,5%。所以它不是一个线性函数,因此对于所有递归的数字来说并不合理。我提到 DateTime 是因为我过去自己也遇到过问题,因为 DateTime 没有使用高精度计时器。
  • @JerriKangasniemi 我可以建议你修复它并发布答案
  • 如果我没看错你的结果,你报告 Any 的速度只有 Exists 的 2 到 3 倍。我看不出数据如何温和地支持您的断言,即“看起来 Any 会慢 2 倍”。当然,它要慢一些,而不是数量级。
【解决方案4】:

作为Matas' answer 基准测试的延续。

TL/DR:Exists() 和 Any() 速度一样快。

首先:使用 Stopwatch 进行基准测试并不精确 (see series0ne's answer on a different, but similiar, topic),但它比 DateTime 精确得多。

获得真正精确读数的方法是使用性能分析。但是,了解这两种方法的性能如何衡量的一种方法是执行这两种方法 loads 次,然后比较每个方法的最快执行时间.这样一来,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 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

细微差别,但差别太小,无法用背景噪音来解释。我的猜测是,如果一个人会做 10 000 或 100 000 Exists()Any(),那么这种细微的差异或多或少会消失。

【讨论】:

  • 我可以建议你做 10 000 和 100 000 和 1000000,只是为了对此有条不紊,还有为什么是最小值而不是平均值?
  • 最小值是因为我想比较每种方法的最快执行(=可能是最少的背景噪音)。我可能会做更多的迭代,虽然会更晚(我怀疑我的老板愿意为此付钱给我,而不是通过我们的积压工作)
  • 我问过 Paul Lindberg,他说没关系 ;) 关于最低限度,我可以看到你的推理,但更正统的方法是使用平均 en.wikipedia.org/wiki/Algorithmic_efficiency#Practice
  • 如果您发布的代码是您实际执行的代码,那么您得到相似的结果也就不足为奇了,因为您在两个测量中都调用 Exists。 ;)
  • Random r = new Random(); 真的需要退出for循环。
【解决方案5】:

当您更正测量值时 - 如上所述:Any and Exists,并添加平均值 - 我们将得到以下输出:

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.

【讨论】:

    【解决方案6】:

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

    【讨论】:

      猜你喜欢
      • 2012-09-07
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 2017-10-19
      • 2019-06-07
      • 2011-05-23
      • 2011-01-15
      相关资源
      最近更新 更多