LINQ .Qualquer VS .Exists - Qual a diferença?


413

Usando o LINQ em coleções, qual é a diferença entre as seguintes linhas de código?

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

e

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

Atualização 1

Quando desmonte .Exists, parece que não há código.

Atualização 2

Alguém sabe por que não existe um código para este?


9
Como é o código que você compilou? Como você desmontou? ildasm? O que você esperava encontrar, mas não encontrou?
Meinersbur

Respostas:


423

Ver documentação

List.Exists (método de objeto - MSDN)

Determina se a Lista (T) contém elementos que correspondem às condições definidas pelo predicado especificado.

Isso existe desde o .NET 2.0, portanto, antes do LINQ. Era para ser usado com o delegado do Predicado , mas as expressões lambda são compatíveis com versões anteriores. Além disso, apenas a List possui isso (nem mesmo o IList)

IEnumerable.Any (método de extensão - MSDN)

Determina se algum elemento de uma sequência satisfaz uma condição.

Isso é novo no .NET 3.5 e usa Func (TSource, bool) como argumento; portanto, ele deveria ser usado com expressões lambda e LINQ.

No comportamento, estes são idênticos.


4
Posteriormente, publiquei um outro tópico em que listei todos os "equivalentes" do Linq dos List<>métodos de instância do .NET 2 .
Jeppe Stig Nielsen

201

A diferença é que Any é um método de extensão para qualquer IEnumerable<T>definido em System.Linq.Enumerable. Pode ser usado em qualquer IEnumerable<T>instância.

Existe não parece ser um método de extensão. Meu palpite é que coll é do tipo List<T>. Se houver, existe um método de instância que funciona muito semelhante a Qualquer.

Em suma , os métodos são essencialmente os mesmos. Um é mais geral que o outro.

  • Qualquer um também tem uma sobrecarga que não aceita parâmetros e simplesmente procura por qualquer item no enumerável.
  • Existe não existe essa sobrecarga.

13
Bem colocado (+1). A lista <T> .Exists existe desde o .Net 2, mas funciona apenas para listas genéricas. IEnumerable <T> .Any foi adicionado no .Net 3 como uma extensão que funciona em qualquer coleção enumerável. Também existem membros semelhantes, como List <T> .Count, que é uma propriedade, e IEnumerable <T> .Count () - um método.
181 Keith

51

TLDR; O desempenho Anyparece mais lento (se eu tiver configurado isso corretamente para avaliar os dois valores quase ao mesmo tempo)

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

            }

gerador de lista de testes:

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;
    }

Com 10 milhões de registros

"Qualquer: 00: 00: 00.3770377 Existe: 00: 00: 00.2490249"

Com registros de 5 milhões

"Qualquer: 00: 00: 00.0940094 Existe: 00: 00: 00.1420142"

Com 1 milhão de registros

"Qualquer: 00: 00: 00.0180018 Existe: 00: 00: 00.0090009"

Com 500k, (também inverti a ordem na qual eles são avaliados para ver se não há nenhuma operação adicional associada ao que ocorrer primeiro).

"Existe: 00: 00: 00.0050005 Qualquer: 00: 00: 00.0100010"

Com 100 mil registros

"Existe: 00: 00: 00.0010001 Qualquer: 00: 00: 00.0020002"

Parece Anyser mais lento pela magnitude de 2.

Edit: Para registros de 5 e 10 milhões, mudei a maneira como gera a lista e, de Existsrepente, fiquei mais lento do que o Anyque implica que há algo errado na maneira como estou testando.

Novo mecanismo de teste:

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: Ok, para eliminar qualquer influência da geração de dados de teste, escrevi tudo para arquivar e agora li a partir daí.

 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 milhões

"Qualquer: 00: 00: 00.1640164 Existe: 00: 00: 00.0750075"

5M

"Qualquer: 00: 00: 00.0810081 Existe: 00: 00: 00.0360036"

1 milhão

"Qualquer: 00: 00: 00.0190019 Existe: 00: 00: 00.0070007"

500k

"Qualquer: 00: 00: 00.0120012 Existe: 00: 00: 00.0040004"

insira a descrição da imagem aqui


3
Não há descrédito para você, mas estou me sentindo cético em relação a esses benchmarks. Veja os números: todo resultado tem uma recursão acontecendo (3770377: 2490249). Pelo menos para mim, isso é um sinal claro de que algo não está correto. Não tenho cem por cento de certeza sobre a matemática aqui, mas acho que as chances desse padrão recorrente acontecer são de 1 em 999 ^ 999 (ou 999! Talvez?) Por valor. Portanto, a chance de acontecer 8 vezes seguidas é infinitesimal. Eu acho que é porque você usa o DateTime para fazer comparações .
Jerri Kangasniemi

@JerriKangasniemi Repetir a mesma operação isoladamente deve sempre levar a mesma quantidade de tempo, o mesmo vale para repeti-la várias vezes. O que faz você dizer que é DateTime?
Matas Vaitkevicius

Claro que sim. O problema ainda é que é extremamente improvável que leve, por exemplo, 0120012 segundos para chamadas de 500k. E se fosse perfeitamente linear, explicando os números de maneira tão agradável, as chamadas da 1M levariam 0240024 segundos (duas vezes mais), mas esse não é o caso. 1M de chamadas leva 58, (3)% a mais que 500k e 10M leva 102,5% a mais que 5M. Portanto, não é uma função linear e, portanto, não é realmente razoável que os números sejam recursivos. Eu mencionei o DateTime porque tive problemas com ele no passado, por causa do DateTime não usar temporizadores de alta precisão.
Jerri Kangasniemi

2
@JerriKangasniemi Posso sugerir que você o corrija e poste uma resposta
Matas Vaitkevicius

1
Se estou lendo seus resultados corretamente, você relatou Qualquer um com apenas 2 a 3 vezes a velocidade de Existe. Não vejo como os dados suportam levemente sua afirmação de que "parece que qualquer um é mais lento pela magnitude de 2". É um pouco mais lento, com certeza, não ordens de magnitude.
Suncat2000

16

Como continuação da resposta de Matas sobre benchmarking.

TL / DR : existe () e Qualquer () são igualmente rápidos.

Primeiro: o benchmarking usando o cronômetro não é preciso ( consulte a resposta da série0ne sobre um tópico diferente, mas semelhante ), mas é muito mais preciso que o DateTime.

A maneira de obter leituras realmente precisas é usando o Performance Profiling. Mas uma maneira de ter uma noção de como medir o desempenho dos dois métodos-se uns aos outros é através da execução de ambos os métodos cargas de vezes e, em seguida, comparando o tempo de execução mais rápida de cada um. Dessa forma, realmente não importa que o JIT e outros ruídos nos proporcionem leituras ruins (e o fazem ), porque ambas as execuções são " igualmente equivocadas " em certo sentido.

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;
    }

Depois de executar o código acima 4 vezes (que por sua vez faz 1 000 Exists()e Any()em uma lista com 1 000 000 elementos), não é difícil ver que os métodos são praticamente igualmente rápidos.

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

Não é uma pequena diferença, mas é diferença que um pequeno demais para não ser explicado pelo ruído de fundo. Meu palpite seria que, se um faria 10 000 ou 100 000 Exists()e Any()em vez disso, que leve diferença desapareceria mais ou menos.


Posso sugerir que você faça 10.000 e 100.000 e 1000000, apenas para ser metódico sobre isso, também por que valor mínimo e não médio?
Matas Vaitkevicius

2
O valor mínimo é porque eu quero comparar a execução mais rápida (= provavelmente a menor quantidade de ruído de fundo) de cada método. Eu poderia fazê-lo com mais iterações, embora seja mais tarde (eu duvido que meu chefe quer me pagar para fazer isso em vez de trabalhar através do nosso backlog)
Jerri Kangasniemi

Perguntei a Paul Lindberg e ele disse que está tudo bem;) em relação ao mínimo, posso ver seu raciocínio, no entanto, uma abordagem mais ortodoxa é usar a média en.wikipedia.org/wiki/Algorithmic_efficiency#Practice
Matas Vaitkevicius

9
Se o código que você postou é o que você realmente executou, não surpreende que você obtenha resultados semelhantes, como você chama Exists em ambas as medições. ;)
Simon Touchtech 17/17/17

Heh, sim, eu também vi isso agora, você diz. Não na minha execução, no entanto. Isso era apenas um conceito simplificado do que eu estava comparando. : P
Jerri Kangasniemi

4

Além disso, isso só funcionará se Valor for do tipo bool. Normalmente, isso é usado com predicados. Qualquer predicado geralmente seria usado para descobrir se existe algum elemento que satisfaça uma determinada condição. Aqui você está apenas fazendo um mapa do seu elemento i para uma propriedade bool. Ele procurará um "i" cuja propriedade Value seja verdadeira. Uma vez feito, o método retornará verdadeiro.


3

Quando você corrige as medidas - como mencionado acima: Qualquer e Existe, e adicionando média -, obteremos a seguinte saída:

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.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.