LINQ .Any VS .Exists - ¿Cuál es la diferencia?

413

Usando LINQ en colecciones, ¿cuál es la diferencia entre las siguientes líneas de código?

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

y

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

Actualización 1

Cuando desarmo .Existsparece que no hay código.

Actualización 2

Alguien sabe por qué no hay código para este?

Anthony D
fuente
99
¿Cómo se ve el código que compiló? ¿Cómo desmontaste? ildasm? ¿Qué esperabas encontrar pero no lo hiciste?
Meinersbur

Respuestas:

423

Ver documentación

List.Exists (Método del objeto - MSDN)

Determina si la Lista (T) contiene elementos que coinciden con las condiciones definidas por el predicado especificado.

Esto existe desde .NET 2.0, por lo tanto, antes de LINQ. Se debe utilizar con el delegado Predicate , pero las expresiones lambda son compatibles con versiones anteriores. Además, solo List tiene esto (ni siquiera IList)

IEnumerable.Any (Método de extensión - MSDN)

Determina si algún elemento de una secuencia cumple una condición.

Esto es nuevo en .NET 3.5 y usa Func (TSource, bool) como argumento, por lo que estaba destinado a usarse con expresiones lambda y LINQ.

En comportamiento, estos son idénticos.

Meinersbur
fuente
44
Más tarde hice una publicación en otro hilo donde enumeré todos los "equivalentes" de Linq de los List<>métodos de instancia de .NET 2 .
Jeppe Stig Nielsen
201

La diferencia es que Any es un método de extensión para cualquier IEnumerable<T>definido en System.Linq.Enumerable. Se puede usar en cualquier IEnumerable<T>instancia.

Existe no parece ser un método de extensión. Supongo que coll es de tipo List<T>. Si es así, existe un método de instancia que funciona de manera muy similar a Any.

En resumen , los métodos son esencialmente los mismos. Uno es más general que el otro.

  • Cualquiera también tiene una sobrecarga que no toma parámetros y simplemente busca cualquier elemento en el enumerable.
  • Existe no tiene tal sobrecarga.
JaredPar
fuente
13
Bien puesto (+1). List <T> .Exists ha existido desde .Net 2, pero solo funciona para listas genéricas. IEnumerable <T> .Any se agregó en .Net 3 como una extensión que funciona en cualquier colección enumerable. También hay miembros similares como List <T> .Count, que es una propiedad e IEnumerable <T> .Count () - un método.
Keith
51

TLDR; En cuanto al rendimiento, Anyparece ser más lento (si configuré esto correctamente para evaluar ambos valores casi al mismo tiempo)

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

            }

generador de lista de prueba:

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

Con 10 millones de registros

"Cualquiera: 00: 00: 00.3770377 Existe: 00: 00: 00.2490249"

Con 5 millones de registros

"Cualquiera: 00: 00: 00.0940094 existe: 00: 00: 00.1420142"

Con registros de 1M

"Cualquiera: 00: 00: 00.0180018 existe: 00: 00: 00.0090009"

Con 500k (también cambié el orden en el que se evalúan para ver si no hay ninguna operación adicional asociada con lo que se ejecute primero).

"Existe: 00: 00: 00.0050005 Cualquiera: 00: 00: 00.0100010"

Con 100k registros

"Existe: 00: 00: 00.0010001 Cualquiera: 00: 00: 00.0020002"

Parecería Anyser más lento por una magnitud de 2.

Editar: para los registros de 5 y 10 millones, cambié la forma en que genera la lista y de Existsrepente me volví más lento de lo Anyque implica que hay algo mal en la forma en que estoy probando.

Nuevo mecanismo de prueba:

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 cualquier influencia de la generación de datos de prueba, lo escribí todo en el archivo y ahora lo leí desde allí.

 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 millones

"Cualquiera: 00: 00: 00.1640164 Existe: 00: 00: 00.0750075"

5 millones

"Cualquiera: 00: 00: 00.0810081 existe: 00: 00: 00.0360036"

1M

"Cualquiera: 00: 00: 00.0190019 existe: 00: 00: 00.0070007"

500k

"Cualquiera: 00: 00: 00.0120012 Existe: 00: 00: 00.0040004"

ingrese la descripción de la imagen aquí

Matas Vaitkevicius
fuente
3
No te desacredito, pero me siento escéptico sobre estos puntos de referencia. Mire los números: cada resultado tiene una recurrencia (3770377: 2490249). Al menos para mí, esa es una señal segura de que algo no es correcto. No estoy cien por ciento seguro de las matemáticas aquí, pero creo que las probabilidades de que ese patrón recurrente ocurra es de 1 en 999 ^ 999 (¿o 999! ¿Tal vez?) Por valor. Entonces, la posibilidad de que ocurra 8 veces seguidas es infinitesimal. Creo que es porque usas DateTime para la evaluación comparativa .
Jerri Kangasniemi
@JerriKangasniemi Repetir la misma operación de forma aislada siempre debe tomar la misma cantidad de tiempo, lo mismo ocurre con la repetición varias veces. ¿Qué te hace decir que es DateTime?
Matas Vaitkevicius
Claro que lo hace. El problema sigue siendo que es muy poco probable que tome, por ejemplo, 0120012 segundos para llamadas de 500k. Y si fuera perfectamente lineal, explicando los números tan bien, las llamadas 1M habrían tomado 0240024 segundos (el doble de tiempo), sin embargo, ese no es el caso. 1 millón de llamadas tarda 58, (3)% más de 500k y 10 millones tarda 102,5% más de 5 millones. Por lo tanto, no es una función lineal y, por lo tanto, no es realmente razonable que los números se repitan. Mencioné DateTime porque he tenido problemas con él en el pasado, debido a que DateTime no usa temporizadores de alta precisión.
Jerri Kangasniemi
2
@JerriKangasniemi ¿Podría sugerirle que lo arregle y publique una respuesta?
Matas Vaitkevicius
1
Si estoy leyendo tus resultados correctamente, informaste que Any es solo de 2 a 3 veces la velocidad de Exists. No veo cómo los datos incluso apoyan levemente su afirmación de que "Parecería que Cualquiera sería más lento en una magnitud de 2". Es un poco más lento, claro, no órdenes de magnitud.
Suncat2000
16

Como continuación de la respuesta de Matas sobre benchmarking.

TL / DR : Exists () y Any () son igualmente rápidos.

En primer lugar: la evaluación comparativa con el cronómetro no es precisa ( consulte la respuesta de series0ne sobre un tema diferente, pero similar ), pero es mucho más preciso que DateTime.

La forma de obtener lecturas realmente precisas es mediante el Perfil de rendimiento. Pero una forma de tener una idea de cómo se comparan el rendimiento de los dos métodos es ejecutando ambos métodos muchas veces y luego comparando el tiempo de ejecución más rápido de cada uno. De esa manera, realmente no importa que JITing y otros ruidos nos den malas lecturas (y lo hace ), porque ambas ejecuciones son " igualmente equivocadas " en cierto 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;
    }

Después de ejecutar el código anterior 4 veces (que a su vez tiene 1 000 Exists()y Any()en una lista con 1 000 000 elementos), no es difícil ver que los métodos son casi igual de 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

No es una ligera diferencia, pero es una diferencia demasiado pequeña como para no ser explicado por el ruido de fondo. Supongo que si uno hiciera 10 000 o 100 000 Exists()y Any(), en cambio, esa ligera diferencia desapareciera más o menos.

Jerri Kangasniemi
fuente
¿Puedo sugerirle que haga 10 000 y 100 000 y 1000000, solo para ser metódico al respecto, también por qué valor mínimo y no promedio?
Matas Vaitkevicius
2
El valor mínimo es porque quiero comparar la ejecución más rápida (= probablemente la menor cantidad de ruido de fondo) de cada método. Podría hacerlo con más iteraciones, aunque será más tarde (dudo que mi jefe quiera pagarme por hacerlo en lugar de trabajar a través de nuestra cartera de pedidos)
Jerri Kangasniemi
Le pregunté a Paul Lindberg y él dice que está bien;) en lo que respecta al mínimo, puedo ver su razonamiento, sin embargo, un enfoque más ortodoxo es usar el promedio en.wikipedia.org/wiki/Algorithmic_efficiency#Practice
Matas Vaitkevicius
99
Si el código que publicó es el que realmente ejecutó, no es sorprendente que obtenga resultados similares, ya que llama a Exists en ambas mediciones. ;)
Simon Touchtech
Je, sí, también vi eso ahora que lo dices. Aunque no en mi ejecución. Esto fue solo un concepto simplificado de lo que estaba comparando. : P
Jerri Kangasniemi
4

Además, esto solo funcionará si Value es de tipo bool. Normalmente esto se usa con predicados. Cualquier predicado se usaría generalmente para determinar si hay algún elemento que satisfaga una condición dada. Aquí solo está haciendo un mapa desde su elemento i a una propiedad bool. Buscará una "i" cuya propiedad Value sea verdadera. Una vez hecho esto, el método devolverá verdadero.

flq
fuente
3

Cuando corrija las mediciones, como se mencionó anteriormente: Cualquiera y existe, y agregando el promedio, obtendremos el siguiente resultado:

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.
jasmintmp
fuente