Comparar dos conjuntos de bytes en .NET


541

¿Cómo puedo hacer esto rápido?

Claro que puedo hacer esto:

static bool ByteArrayCompare(byte[] a1, byte[] a2)
{
    if (a1.Length != a2.Length)
        return false;

    for (int i=0; i<a1.Length; i++)
        if (a1[i]!=a2[i])
            return false;

    return true;
}

Pero estoy buscando una función BCL o alguna forma probada altamente optimizada para hacer esto.

java.util.Arrays.equals((sbyte[])(Array)a1, (sbyte[])(Array)a2);

funciona bien, pero no parece que funcione para x64.

Tenga en cuenta mi respuesta súper rápida aquí .


1
"Esto cuenta un poco con el hecho de que las matrices comienzan qword alineadas". Eso es un gran si. Debes arreglar el código para reflejar eso.
Joe Chung el

44
devuelve a1.Length == a2.Length &&! a1.Where ((t, i) => t! = a2 [i]). Any ();
alerya

Me gustó la respuesta de @OhadSchneider sobreIStructuralEquatable
LCJ

Respuestas:


613

Puede usar el método Enumerable.SequenceEqual .

using System;
using System.Linq;
...
var a1 = new int[] { 1, 2, 3};
var a2 = new int[] { 1, 2, 3};
var a3 = new int[] { 1, 2, 4};
var x = a1.SequenceEqual(a2); // true
var y = a1.SequenceEqual(a3); // false

Si no puede usar .NET 3.5 por alguna razón, su método está bien.
El entorno de compilador \ tiempo de ejecución optimizará su ciclo para que no tenga que preocuparse por el rendimiento.


44
Pero, ¿SequenceEqual no tarda más en procesarse que una comparación insegura? ¿Especialmente cuando estás haciendo miles de comparaciones?
tcables

90
Sí, esto corre aproximadamente 50 veces más lento que la comparación insegura.
Hafthor

27
Esto realmente está levantando a los muertos aquí, pero lento es realmente una mala palabra para usar aquí. 50 veces más lento suena mal, pero no es frecuente que esté comparando datos suficientes para que marque la diferencia, y si es así, realmente necesita comparar esto para su propio caso, por una miríada de razones. Por ejemplo, tenga en cuenta que el creador de la respuesta insegura observa una diferencia de 7 veces más lenta, en lugar de 50 veces más lenta (la velocidad del método inseguro también depende de la alineación de los datos). En los raros casos en que estos números importan, P / Invoke es aún más rápido.
Selali Adobor

44
Entonces, ¿la implementación más lenta obtiene más de 300 me gusta? Sugeriría conectar el msvcrt.dll ya que esa sería la forma más rápida de hacer el trabajo.
TGarrett

69
Lo más rápido no es lo más importante para un negocio. La capacidad de mantenimiento es mucho más "rápida" que los ahorros en este código equivaldrán al 99% de los casos. Estoy usando SequenceEqual y mi código completo es <1 ms. Esos µs que está guardando nunca se sumarán a los 5 minutos de falta de legibilidad de P / Invoke.
PRMan

236

P / Invocar poderes activados!

[DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl)]
static extern int memcmp(byte[] b1, byte[] b2, long count);

static bool ByteArrayCompare(byte[] b1, byte[] b2)
{
    // Validate buffers are the same length.
    // This also ensures that the count does not exceed the length of either buffer.  
    return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0;
}

48
P / Invocar yaay: esto resultó ser el más rápido con diferencia en bitmaps al menos: stackoverflow.com/questions/2031217/…
Erik Forbes

25
La fijación no es necesaria en este caso. El mariscal realiza la fijación automática cuando llama a código nativo con PInvoke. Referencia: stackoverflow.com/questions/2218444/…
Mark Glasgow

14
P / Invoke puede provocar abucheos, pero es, con mucho, la más rápida de todas las soluciones presentadas, incluida una implementación que se me ocurrió que utiliza comparaciones de tamaño de puntero inseguras. Sin embargo, puede hacer algunas optimizaciones antes de llamar al código nativo, incluida la igualdad de referencia y la comparación del primer y último elemento.
Josh

38
¿Por qué el abucheo? Poster quería una implementación rápida y una comparación optimizada del lenguaje ensamblador no puede ser mejor. No sé cómo obtener un "REPE CMPSD" de .NET sin P / INVOKE.
Jason Goemaat

14
Nitpick: el código de usuario no debe utilizar MSVCR.dll. Para usar el MSVCR, tendría que distribuir el tiempo de ejecución con la versión que distribuye. ( msdn.microsoft.com/en-us/library/… y blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.aspx )
Mitch

160

Hay una nueva solución integrada para esto en .NET 4: IStructuralEquatable

static bool ByteArrayCompare(byte[] a1, byte[] a2) 
{
    return StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2);
}

17
Según esta publicación de blog , en realidad es muy lento.
Matt Johnson-Pint

48
Loco lento. Aproximadamente 180 veces más lento que el simple para el bucle.
Hafthor

Esto funciona, pero no entiendo por qué. Un byte [] es un tipo primitivo que no implementa IStructuralEquatable, entonces, ¿por qué puede lanzarlo? ¡Y un lanzamiento implícito! Y entonces el método de interfaz "Igual" está disponible mágicamente ... ¿de dónde viene la implementación de ese método? Puede alguien ponerme al tanto?
Josh

1
¿Por qué no solo StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2)? No hay NullReferenceExceptions aquí.
ta.speot.is el

1
@ ta.speot.is ¡Gracias, no puedo discutir con un trazador de líneas! La solución anterior fue un poco más eficiente ya que guardó el reparto en IStructuralEquatable (se sabe que una matriz está estática para IStructuralEquatable), pero de hecho sus sugerencias hacen que el método funcione también para argumentos nulos.
Ohad Schneider

76

El usuario gil sugirió un código inseguro que generó esta solución:

// Copyright (c) 2008-2013 Hafthor Stefansson
// Distributed under the MIT/X11 software license
// Ref: http://www.opensource.org/licenses/mit-license.php.
static unsafe bool UnsafeCompare(byte[] a1, byte[] a2) {
  if(a1==a2) return true;
  if(a1==null || a2==null || a1.Length!=a2.Length)
    return false;
  fixed (byte* p1=a1, p2=a2) {
    byte* x1=p1, x2=p2;
    int l = a1.Length;
    for (int i=0; i < l/8; i++, x1+=8, x2+=8)
      if (*((long*)x1) != *((long*)x2)) return false;
    if ((l & 4)!=0) { if (*((int*)x1)!=*((int*)x2)) return false; x1+=4; x2+=4; }
    if ((l & 2)!=0) { if (*((short*)x1)!=*((short*)x2)) return false; x1+=2; x2+=2; }
    if ((l & 1)!=0) if (*((byte*)x1) != *((byte*)x2)) return false;
    return true;
  }
}

que hace una comparación basada en 64 bits para la mayor cantidad posible de la matriz. Este tipo de cuenta con el hecho de que las matrices comienzan qword alineado. Funcionará si no qword alineado, solo que no tan rápido como si lo fuera.

Realiza aproximadamente siete temporizadores más rápido que el forbucle simple . El uso de la biblioteca J # se realizó de manera equivalente al forbucle original . El uso de .SequenceEqual se ejecuta siete veces más lento; Creo que solo porque está usando IEnumerator.MoveNext. Me imagino que las soluciones basadas en LINQ son al menos tan lentas o peores.


3
Buena solución Pero una pista (pequeña): una comparación si las referencias a1 y a2 son iguales puede acelerar las cosas si uno da la misma matriz para a1 y b1.
mmmmmmmm

12
Nuevos datos de prueba en la versión .NET 4 x64: IStructualEquatable.equals ~ 180x más lento, SequenceEqual 15x más lento, SHA1 hash compare 11x más lento, bitconverter ~ mismo, inseguro 7x más rápido, pinvoke 11x más rápido. Es genial que inseguro sea solo un poco más lento que P / Invoke en memcmp.
Hafthor

3
Este enlace brinda buenos detalles sobre por qué la alineación de la memoria es importante ibm.com/developerworks/library/pa-dalign , por lo que una optimización podría ser verificar la alineación y, si ambas matrices están desalineadas en la misma cantidad, compare los bytes hasta que estén ambos en un límite de qword.
Hafthor

55
¿no daría esto falso cuando a1 y a2 son nulos?
nawfal

2
@CristiDiaconescu He repetido la respuesta de KevinDriedger. Lo que probablemente debería hacer es hacer que el conjunto de pruebas y mis resultados estén disponibles en github y vincularlos a mi respuesta.
Hafthor

74

Span<T> ofrece una alternativa extremadamente competitiva sin tener que tirar pelusas confusas y / o no portátiles en la base de código de su propia aplicación:

// byte[] is implicitly convertible to ReadOnlySpan<byte>
static bool ByteArrayCompare(ReadOnlySpan<byte> a1, ReadOnlySpan<byte> a2)
{
    return a1.SequenceEqual(a2);
}

La implementación (de las entrañas de) de .NET Core 3.1.0 se puede encontrar aquí .

He revisé @ quid de EliArbel añadir este método SpansEqual, la caída de la mayoría de los artistas menos interesantes en los puntos de referencia de los demás, funciona con diferentes tamaños de matriz, gráficos de salida, y la marca SpansEqualcomo línea de base para que se informa de cómo los diferentes métodos se comparan con SpansEqual.

Los siguientes números son de los resultados, ligeramente editados para eliminar la columna "Error".

|        Method |  ByteCount |               Mean |            StdDev | Ratio |
|-------------- |----------- |-------------------:|------------------:|------:|
|    SpansEqual |         15 |           3.562 ns |         0.0035 ns |  1.00 |
|  LongPointers |         15 |           4.611 ns |         0.0028 ns |  1.29 |
|      Unrolled |         15 |          18.035 ns |         0.0195 ns |  5.06 |
| PInvokeMemcmp |         15 |          11.210 ns |         0.0353 ns |  3.15 |
|               |            |                    |                   |       |
|    SpansEqual |       1026 |          20.048 ns |         0.0286 ns |  1.00 |
|  LongPointers |       1026 |          63.347 ns |         0.1062 ns |  3.16 |
|      Unrolled |       1026 |          39.175 ns |         0.0304 ns |  1.95 |
| PInvokeMemcmp |       1026 |          40.830 ns |         0.0350 ns |  2.04 |
|               |            |                    |                   |       |
|    SpansEqual |    1048585 |      44,070.526 ns |        35.3348 ns |  1.00 |
|  LongPointers |    1048585 |      59,973.407 ns |        80.4145 ns |  1.36 |
|      Unrolled |    1048585 |      55,032.945 ns |        24.4745 ns |  1.25 |
| PInvokeMemcmp |    1048585 |      55,593.719 ns |        22.4301 ns |  1.26 |
|               |            |                    |                   |       |
|    SpansEqual | 2147483591 | 253,648,180.000 ns | 1,112,524.3074 ns |  1.00 |
|  LongPointers | 2147483591 | 249,412,064.286 ns | 1,079,409.5670 ns |  0.98 |
|      Unrolled | 2147483591 | 246,329,091.667 ns |   852,021.7992 ns |  0.97 |
| PInvokeMemcmp | 2147483591 | 247,795,940.000 ns | 3,390,676.3644 ns |  0.98 |

Me sorprendió ver que SpansEqualno estaba en la cima de los métodos de tamaño máximo de matriz, pero la diferencia es tan pequeña que no creo que importe.

Mi información del sistema:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.1.100
  [Host]     : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
  DefaultJob : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT

Nunca pensé que usaría Span <T> o algo parecido en todo lo que hago. Gracias a ti ahora puedo presumir de esto a mis compañeros de trabajo.
jokab

¿SequenceEqual se implementa especialmente como un método Span? Pensé que era solo uno de los métodos de extensión IEnumerable.
Zastai

1
@Zastai sí, {ReadOnly,}Span<T>tiene su propia versión de SequenceEqual(mismo nombre porque tiene el mismo contrato que el IEnumerable<T>método de extensión correspondiente , es más rápido). Tenga en cuenta que {ReadOnly,}Span<T>no puede usar IEnumerable<T>métodos de extensión debido a las restricciones en los ref structtipos.
Joe Amenta

1
@Sentinel, el paquete System.Memory tiene Span<T>implementaciones "portátiles" / "lentas" para netstandard1.1y superiores (así que juega con este gráfico interactivo para ver cuáles son). "Rápido" Span<T>solo está disponible en .NET Core 2.1, en este momento, pero tenga en cuenta que SequenceEqual<T>, específicamente, debería haber muy poca diferencia entre "rápido" y "lento" / "portátil" (aunque los netstandard2.0objetivos deberían ver una ligera mejora porque tener la ruta del código vectorizado).
Joe Amenta

1
install-package system.memory
Chris Moschini

30

Si no se opone a hacerlo, puede importar el ensamblado J # "vjslib.dll" y usar su método Arrays.equals (byte [], byte []) ...

Aunque no me culpes si alguien se ríe de ti ...


EDITAR: por lo poco que vale, utilicé Reflector para desmontar el código para eso, y así es como se ve:

public static bool equals(sbyte[] a1, sbyte[] a2)
{
  if (a1 == a2)
  {
    return true;
  }
  if ((a1 != null) && (a2 != null))
  {
    if (a1.Length != a2.Length)
    {
      return false;
    }
    for (int i = 0; i < a1.Length; i++)
    {
      if (a1[i] != a2[i])
      {
        return false;
      }
    }
    return true;
  }
  return false;
}

25

.NET 3.5 y posteriores tienen un nuevo tipo público, System.Data.Linq.Binaryque encapsula byte[]. Implementa IEquatable<Binary>que (en efecto) compara dos conjuntos de bytes. Tenga en cuenta que System.Data.Linq.Binarytambién tiene un operador de conversión implícito de byte[].

Documentación de MSDN: System.Data.Linq.Binary

Reflector de descompilación del método Equals:

private bool EqualsTo(Binary binary)
{
    if (this != binary)
    {
        if (binary == null)
        {
            return false;
        }
        if (this.bytes.Length != binary.bytes.Length)
        {
            return false;
        }
        if (this.hashCode != binary.hashCode)
        {
            return false;
        }
        int index = 0;
        int length = this.bytes.Length;
        while (index < length)
        {
            if (this.bytes[index] != binary.bytes[index])
            {
                return false;
            }
            index++;
        }
    }
    return true;
}

Un giro interesante es que solo proceden al bucle de comparación byte por byte si los hashes de los dos objetos binarios son iguales. Sin embargo, esto tiene el costo de calcular el hash en el constructor de Binaryobjetos (atravesando la matriz con forloop :-)).

La implementación anterior significa que, en el peor de los casos, es posible que tenga que atravesar las matrices tres veces: primero para calcular el hash de array1, luego para calcular el hash de array2 y finalmente (porque este es el peor de los casos, longitudes y hashes iguales) para comparar bytes en matriz1 con bytes en matriz 2.

En general, aunque System.Data.Linq.Binaryestá integrado en BCL, no creo que sea la forma más rápida de comparar dos conjuntos de bytes: - |.


20

Publiqué una pregunta similar sobre cómo verificar si el byte [] está lleno de ceros. (El código SIMD fue eliminado, así que lo eliminé de esta respuesta). Aquí está el código más rápido de mis comparaciones:

static unsafe bool EqualBytesLongUnrolled (byte[] data1, byte[] data2)
{
    if (data1 == data2)
        return true;
    if (data1.Length != data2.Length)
        return false;

    fixed (byte* bytes1 = data1, bytes2 = data2) {
        int len = data1.Length;
        int rem = len % (sizeof(long) * 16);
        long* b1 = (long*)bytes1;
        long* b2 = (long*)bytes2;
        long* e1 = (long*)(bytes1 + len - rem);

        while (b1 < e1) {
            if (*(b1) != *(b2) || *(b1 + 1) != *(b2 + 1) || 
                *(b1 + 2) != *(b2 + 2) || *(b1 + 3) != *(b2 + 3) ||
                *(b1 + 4) != *(b2 + 4) || *(b1 + 5) != *(b2 + 5) || 
                *(b1 + 6) != *(b2 + 6) || *(b1 + 7) != *(b2 + 7) ||
                *(b1 + 8) != *(b2 + 8) || *(b1 + 9) != *(b2 + 9) || 
                *(b1 + 10) != *(b2 + 10) || *(b1 + 11) != *(b2 + 11) ||
                *(b1 + 12) != *(b2 + 12) || *(b1 + 13) != *(b2 + 13) || 
                *(b1 + 14) != *(b2 + 14) || *(b1 + 15) != *(b2 + 15))
                return false;
            b1 += 16;
            b2 += 16;
        }

        for (int i = 0; i < rem; i++)
            if (data1 [len - 1 - i] != data2 [len - 1 - i])
                return false;

        return true;
    }
}

Medido en dos conjuntos de bytes de 256 MB:

UnsafeCompare                           : 86,8784 ms
EqualBytesSimd                          : 71,5125 ms
EqualBytesSimdUnrolled                  : 73,1917 ms
EqualBytesLongUnrolled                  : 39,8623 ms

1
Confirmo. También corrí las pruebas. Esto es más rápido que la respuesta que usa la llamada no segura de memcmp.
ujeenator

1
@AmberdeBlack ¿Estás seguro? ¿Probaste con pequeños arreglos?
Zar Shardan

@ArekBulski ¿Estás seguro de que esto es más rápido que memcmp, porque mis pruebas muestran lo contrario?
Zar Shardan

Obtuve un rendimiento prácticamente idéntico entre esto y memcmp, así que +1 para una solución totalmente administrada.
Mike Marynowski

10
 using System.Linq; //SequenceEqual

 byte[] ByteArray1 = null;
 byte[] ByteArray2 = null;

 ByteArray1 = MyFunct1();
 ByteArray2 = MyFunct2();

 if (ByteArray1.SequenceEqual<byte>(ByteArray2) == true)
 {
    MessageBox.Show("Match");
 }
 else
 {
   MessageBox.Show("Don't match");
 }

1
Eso es lo que he estado usando. Pero umm ... suena como una comparación secuencial que de lo contrario haría con un bucle simple, por lo tanto, no es muy rápido. Sería bueno reflejarlo y ver qué está haciendo realmente. A juzgar por el nombre, no es nada lujoso.
Sergey Akopov

1
Sí, pero ya mencionado en la respuesta aceptada. por cierto, podría eliminar la especificación de tipo allí.
nawfal

10

¡Agreguemos uno más!

Recientemente, Microsoft lanzó un paquete especial de NuGet, System.Runtime.CompilerServices.Unsafe . Es especial porque está escrito en IL y proporciona una funcionalidad de bajo nivel que no está disponible directamente en C #.

Uno de sus métodos, Unsafe.As<T>(object)permite convertir cualquier tipo de referencia a otro tipo de referencia, omitiendo cualquier verificación de seguridad. Esta suele ser una muy mala idea, pero si ambos tipos tienen la misma estructura, puede funcionar. Entonces podemos usar esto para lanzar un byte[]a a long[]:

bool CompareWithUnsafeLibrary(byte[] a1, byte[] a2)
{
    if (a1.Length != a2.Length) return false;

    var longSize = (int)Math.Floor(a1.Length / 8.0);
    var long1 = Unsafe.As<long[]>(a1);
    var long2 = Unsafe.As<long[]>(a2);

    for (var i = 0; i < longSize; i++)
    {
        if (long1[i] != long2[i]) return false;
    }

    for (var i = longSize * 8; i < a1.Length; i++)
    {
        if (a1[i] != a2[i]) return false;
    }

    return true;
}

Tenga en cuenta que long1.Lengthaún devolvería la longitud de la matriz original, ya que está almacenada en un campo en la estructura de memoria de la matriz.

Este método no es tan rápido como otros métodos demostrados aquí, pero es mucho más rápido que el método ingenuo, no usa código inseguro o P / Invoke o pinning, y la implementación es bastante sencilla (IMO). Estos son algunos resultados de BenchmarkDotNet de mi máquina:

BenchmarkDotNet=v0.10.3.0, OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4870HQ CPU 2.50GHz, ProcessorCount=8
Frequency=2435775 Hz, Resolution=410.5470 ns, Timer=TSC
  [Host]     : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0
  DefaultJob : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0

                 Method |          Mean |    StdDev |
----------------------- |-------------- |---------- |
          UnsafeLibrary |   125.8229 ns | 0.3588 ns |
          UnsafeCompare |    89.9036 ns | 0.8243 ns |
           JSharpEquals | 1,432.1717 ns | 1.3161 ns |
 EqualBytesLongUnrolled |    43.7863 ns | 0.8923 ns |
              NewMemCmp |    65.4108 ns | 0.2202 ns |
            ArraysEqual |   910.8372 ns | 2.6082 ns |
          PInvokeMemcmp |    52.7201 ns | 0.1105 ns |

También he creado una esencia con todas las pruebas .


No utiliza la palabra clave insegura, pero de todas
formas

He actualizado mi NewMemCmprespuesta para usar AVX-2
Sr. Anderson,

8

Desarrollé un método que late ligeramente memcmp()(la respuesta del zócalo) y muy ligeramente EqualBytesLongUnrolled()(la respuesta de Arek Bulski) en mi PC. Básicamente, desenrolla el bucle por 4 en lugar de 8.

Actualización 30 de marzo de 2019 :

A partir de .NET core 3.0, ¡tenemos soporte SIMD!

Esta solución es más rápida por un margen considerable en mi PC:

#if NETCOREAPP3_0
using System.Runtime.Intrinsics.X86;
#endif


public static unsafe bool Compare(byte[] arr0, byte[] arr1)
{
    if (arr0 == arr1)
    {
        return true;
    }
    if (arr0 == null || arr1 == null)
    {
        return false;
    }
    if (arr0.Length != arr1.Length)
    {
        return false;
    }
    if (arr0.Length == 0)
    {
        return true;
    }
    fixed (byte* b0 = arr0, b1 = arr1)
    {
#if NETCOREAPP3_0
        if (Avx2.IsSupported)
        {
            return Compare256(b0, b1, arr0.Length);
        }
        else if (Sse2.IsSupported)
        {
            return Compare128(b0, b1, arr0.Length);
        }
        else
#endif
        {
            return Compare64(b0, b1, arr0.Length);
        }
    }
}
#if NETCOREAPP3_0
public static unsafe bool Compare256(byte* b0, byte* b1, int length)
{
    byte* lastAddr = b0 + length;
    byte* lastAddrMinus128 = lastAddr - 128;
    const int mask = -1;
    while (b0 < lastAddrMinus128) // unroll the loop so that we are comparing 128 bytes at a time.
    {
        if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(b0), Avx.LoadVector256(b1))) != mask)
        {
            return false;
        }
        if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(b0 + 32), Avx.LoadVector256(b1 + 32))) != mask)
        {
            return false;
        }
        if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(b0 + 64), Avx.LoadVector256(b1 + 64))) != mask)
        {
            return false;
        }
        if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(b0 + 96), Avx.LoadVector256(b1 + 96))) != mask)
        {
            return false;
        }
        b0 += 128;
        b1 += 128;
    }
    while (b0 < lastAddr)
    {
        if (*b0 != *b1) return false;
        b0++;
        b1++;
    }
    return true;
}
public static unsafe bool Compare128(byte* b0, byte* b1, int length)
{
    byte* lastAddr = b0 + length;
    byte* lastAddrMinus64 = lastAddr - 64;
    const int mask = 0xFFFF;
    while (b0 < lastAddrMinus64) // unroll the loop so that we are comparing 64 bytes at a time.
    {
        if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(b0), Sse2.LoadVector128(b1))) != mask)
        {
            return false;
        }
        if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(b0 + 16), Sse2.LoadVector128(b1 + 16))) != mask)
        {
            return false;
        }
        if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(b0 + 32), Sse2.LoadVector128(b1 + 32))) != mask)
        {
            return false;
        }
        if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(b0 + 48), Sse2.LoadVector128(b1 + 48))) != mask)
        {
            return false;
        }
        b0 += 64;
        b1 += 64;
    }
    while (b0 < lastAddr)
    {
        if (*b0 != *b1) return false;
        b0++;
        b1++;
    }
    return true;
}
#endif
public static unsafe bool Compare64(byte* b0, byte* b1, int length)
{
    byte* lastAddr = b0 + length;
    byte* lastAddrMinus32 = lastAddr - 32;
    while (b0 < lastAddrMinus32) // unroll the loop so that we are comparing 32 bytes at a time.
    {
        if (*(ulong*)b0 != *(ulong*)b1) return false;
        if (*(ulong*)(b0 + 8) != *(ulong*)(b1 + 8)) return false;
        if (*(ulong*)(b0 + 16) != *(ulong*)(b1 + 16)) return false;
        if (*(ulong*)(b0 + 24) != *(ulong*)(b1 + 24)) return false;
        b0 += 32;
        b1 += 32;
    }
    while (b0 < lastAddr)
    {
        if (*b0 != *b1) return false;
        b0++;
        b1++;
    }
    return true;
}

Mis medidas difieren para .NET 462 puede NETCORE:
Motlicek Petr

El código se bloquea al comparar dos matrices de longitud 0, porque regresa la fijación null.
Glenn Slayden

memcmp no es solo un comparador de acciones. Proporciona información sobre qué objeto es más grande o más pequeño. ¿Puede adoptar su algoritmo para este propósito y verificar el rendimiento?
nicolay.anykienko

¿Es más rápido que Spany memcpy?
silkfire

@silkfire En .NET core 3 y CPU moderna, debería ser 2-3 veces más rápido para matrices grandes.
Sr. Anderson

6

Usaría código inseguro y ejecutaría el forbucle comparando punteros Int32.

Quizás también debería considerar verificar que las matrices no sean nulas.


5

Si observa cómo .NET hace string.Equals, verá que usa un método privado llamado EqualsHelper que tiene una implementación de puntero "insegura". .NET Reflector es tu amigo para ver cómo se hacen las cosas internamente.

Esto se puede usar como una plantilla para la comparación de la matriz de bytes en la que hice una implementación en la publicación del blog Comparación rápida de la matriz de bytes en C # . También hice algunos puntos de referencia rudimentarios para ver cuándo una implementación segura es más rápida que la insegura.

Dicho esto, a menos que realmente necesites un rendimiento excelente, elegiría una simple comparación de bucles fr.


3

No pude encontrar una solución con la que estoy completamente satisfecho (rendimiento razonable, pero sin código / pinvoke inseguro), así que se me ocurrió esto, nada realmente original, pero funciona:

    /// <summary>
    /// 
    /// </summary>
    /// <param name="array1"></param>
    /// <param name="array2"></param>
    /// <param name="bytesToCompare"> 0 means compare entire arrays</param>
    /// <returns></returns>
    public static bool ArraysEqual(byte[] array1, byte[] array2, int bytesToCompare = 0)
    {
        if (array1.Length != array2.Length) return false;

        var length = (bytesToCompare == 0) ? array1.Length : bytesToCompare;
        var tailIdx = length - length % sizeof(Int64);

        //check in 8 byte chunks
        for (var i = 0; i < tailIdx; i += sizeof(Int64))
        {
            if (BitConverter.ToInt64(array1, i) != BitConverter.ToInt64(array2, i)) return false;
        }

        //check the remainder of the array, always shorter than 8 bytes
        for (var i = tailIdx; i < length; i++)
        {
            if (array1[i] != array2[i]) return false;
        }

        return true;
    }

Rendimiento comparado con algunas de las otras soluciones en esta página:

Bucle simple: 19837 ticks, 1.00

* BitConverter: 4886 ticks, 4.06

Comparación insegura: 1636 ticks, 12.12

EqualBytesLongUnrolled: 637 ticks, 31.09

P / invocar memcmp: 369 ticks, 53.67

Probado en linqpad, matrices idénticas de 1000000 bytes (peor de los casos), 500 iteraciones cada una.


Sí, noté que en el comentario de stackoverflow.com/a/1445280/4489 que mis pruebas muestran que esto es en realidad un poco más lento que el bucle simple que tenía en la pregunta original.
Hafthor

¿Estás seguro? ¿En mis pruebas es 4 veces más rápido? Sin embargo, nada supera el buen código nativo antiguo, incluso con la sobrecarga de cálculo de referencias.
Zar Shardan

3

Parece que EqualBytesLongUnrolled es el mejor de lo sugerido anteriormente.

Los métodos omitidos (Enumerable.SequenceEqual, StructuralComparisons.StructuralEqualityComparer.Equals) no fueron pacientes por lento. En matrices de 265MB he medido esto:

Host Process Environment Information:
BenchmarkDotNet.Core=v0.9.9.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-3770 CPU 3.40GHz, ProcessorCount=8
Frequency=3323582 ticks, Resolution=300.8802 ns, Timer=TSC
CLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT]
GC=Concurrent Workstation
JitModules=clrjit-v4.6.1590.0

Type=CompareMemoriesBenchmarks  Mode=Throughput  

                 Method |      Median |    StdDev | Scaled | Scaled-SD |
----------------------- |------------ |---------- |------- |---------- |
             NewMemCopy |  30.0443 ms | 1.1880 ms |   1.00 |      0.00 |
 EqualBytesLongUnrolled |  29.9917 ms | 0.7480 ms |   0.99 |      0.04 |
          msvcrt_memcmp |  30.0930 ms | 0.2964 ms |   1.00 |      0.03 |
          UnsafeCompare |  31.0520 ms | 0.7072 ms |   1.03 |      0.04 |
       ByteArrayCompare | 212.9980 ms | 2.0776 ms |   7.06 |      0.25 |

OS=Windows
Processor=?, ProcessorCount=8
Frequency=3323582 ticks, Resolution=300.8802 ns, Timer=TSC
CLR=CORE, Arch=64-bit ? [RyuJIT]
GC=Concurrent Workstation
dotnet cli version: 1.0.0-preview2-003131

Type=CompareMemoriesBenchmarks  Mode=Throughput  

                 Method |      Median |    StdDev | Scaled | Scaled-SD |
----------------------- |------------ |---------- |------- |---------- |
             NewMemCopy |  30.1789 ms | 0.0437 ms |   1.00 |      0.00 |
 EqualBytesLongUnrolled |  30.1985 ms | 0.1782 ms |   1.00 |      0.01 |
          msvcrt_memcmp |  30.1084 ms | 0.0660 ms |   1.00 |      0.00 |
          UnsafeCompare |  31.1845 ms | 0.4051 ms |   1.03 |      0.01 |
       ByteArrayCompare | 212.0213 ms | 0.1694 ms |   7.03 |      0.01 |

He actualizado mi NewMemCmprespuesta para usar AVX-2
Sr. Anderson,

3

No he visto muchas soluciones linq aquí.

No estoy seguro de las implicaciones de rendimiento, sin embargo, generalmente me mantengo linqcomo regla general y luego optimizo más tarde si es necesario.

public bool CompareTwoArrays(byte[] array1, byte[] array2)
 {
   return !array1.Where((t, i) => t != array2[i]).Any();
 }

Tenga en cuenta que esto solo funciona si son matrices del mismo tamaño. una extensión podría verse así

public bool CompareTwoArrays(byte[] array1, byte[] array2)
 {
   if (array1.Length != array2.Length) return false;
   return !array1.Where((t, i) => t != array2[i]).Any();
 }

El punto central de la pregunta es una solución más rápida que la función publicada en la pregunta.
CodesInChaos

3

Hice algunas mediciones usando el programa adjunto .net 4.7 versión de compilación sin el depurador adjunto. Creo que la gente ha estado usando la métrica incorrecta, ya que lo que le importa si le importa la velocidad aquí es cuánto tiempo lleva averiguar si las matrices de dos bytes son iguales. es decir, rendimiento en bytes.

StructuralComparison :              4.6 MiB/s
for                  :            274.5 MiB/s
ToUInt32             :            263.6 MiB/s
ToUInt64             :            474.9 MiB/s
memcmp               :           8500.8 MiB/s

Como puede ver, no hay mejor manera memcmpy sus órdenes de magnitud son más rápidas. Un forbucle simple es la segunda mejor opción. Y todavía me sorprende por qué Microsoft no puede simplemente incluir un Buffer.Comparemétodo.

[Program.cs]:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace memcmp
{
    class Program
    {
        static byte[] TestVector(int size)
        {
            var data = new byte[size];
            using (var rng = new System.Security.Cryptography.RNGCryptoServiceProvider())
            {
                rng.GetBytes(data);
            }
            return data;
        }

        static TimeSpan Measure(string testCase, TimeSpan offset, Action action, bool ignore = false)
        {
            var t = Stopwatch.StartNew();
            var n = 0L;
            while (t.Elapsed < TimeSpan.FromSeconds(10))
            {
                action();
                n++;
            }
            var elapsed = t.Elapsed - offset;
            if (!ignore)
            {
                Console.WriteLine($"{testCase,-16} : {n / elapsed.TotalSeconds,16:0.0} MiB/s");
            }
            return elapsed;
        }

        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern int memcmp(byte[] b1, byte[] b2, long count);

        static void Main(string[] args)
        {
            // how quickly can we establish if two sequences of bytes are equal?

            // note that we are testing the speed of different comparsion methods

            var a = TestVector(1024 * 1024); // 1 MiB
            var b = (byte[])a.Clone();

            // was meant to offset the overhead of everything but copying but my attempt was a horrible mistake... should have reacted sooner due to the initially ridiculous throughput values...
            // Measure("offset", new TimeSpan(), () => { return; }, ignore: true);
            var offset = TimeZone.Zero

            Measure("StructuralComparison", offset, () =>
            {
                StructuralComparisons.StructuralEqualityComparer.Equals(a, b);
            });

            Measure("for", offset, () =>
            {
                for (int i = 0; i < a.Length; i++)
                {
                    if (a[i] != b[i]) break;
                }
            });

            Measure("ToUInt32", offset, () =>
            {
                for (int i = 0; i < a.Length; i += 4)
                {
                    if (BitConverter.ToUInt32(a, i) != BitConverter.ToUInt32(b, i)) break;
                }
            });

            Measure("ToUInt64", offset, () =>
            {
                for (int i = 0; i < a.Length; i += 8)
                {
                    if (BitConverter.ToUInt64(a, i) != BitConverter.ToUInt64(b, i)) break;
                }
            });

            Measure("memcmp", offset, () =>
            {
                memcmp(a, b, a.Length);
            });
        }
    }
}

2

Para comparar matrices de bytes cortos, lo siguiente es un truco interesante:

if(myByteArray1.Length != myByteArray2.Length) return false;
if(myByteArray1.Length == 8)
   return BitConverter.ToInt64(myByteArray1, 0) == BitConverter.ToInt64(myByteArray2, 0); 
else if(myByteArray.Length == 4)
   return BitConverter.ToInt32(myByteArray2, 0) == BitConverter.ToInt32(myByteArray2, 0); 

Entonces probablemente caería en la solución que figura en la pregunta.

Sería interesante hacer un análisis de rendimiento de este código.


int i = 0; for (; i <a1.Length-7; i + = 8) if (BitConverter.ToInt64 (a1, i)! = BitConverter.ToInt64 (a2, i)) devuelve false; para (; i <a1.Length; i ++) if (a1 [i]! = a2 [i]) return false; volver verdadero; // un poco más lento que simple para loop.
Hafthor

2

Para aquellos de ustedes que se preocupan por el orden (es decir, quieren memcmpque devuelva un intcomo debería en lugar de nada), .NET Core 3.0 (y presumiblemente .NET Standard 2.1, también conocido como .NET 5.0) incluirá un Span.SequenceCompareTo(...)método de extensión (más un Span.SequenceEqualTo) que puede ser usado para comparar dos ReadOnlySpan<T>instancias ( where T: IComparable<T>).

En la propuesta original de GitHub , la discusión incluyó comparaciones de enfoques con cálculos de tablas de salto, lectura de un byte[]as long[], uso de SIMD y p / invocación a la implementación de CLRmemcmp .

En el futuro, este debería ser su método de referencia para comparar matrices de bytes o rangos de bytes (como debería usar en Span<byte>lugar de byte[]sus API de .NET Standard 2.1), y es lo suficientemente rápido como para que ya no deba preocuparse por optimizarlo (y no, a pesar de las similitudes en el nombre, no funciona tan abismalmente como el horrible Enumerable.SequenceEqual).

#if NETCOREAPP3_0
// Using the platform-native Span<T>.SequenceEqual<T>(..)
public static int Compare(byte[] range1, int offset1, byte[] range2, int offset2, int count)
{
    var span1 = range1.AsSpan(offset1, count);
    var span2 = range2.AsSpan(offset2, count);

    return span1.SequenceCompareTo(span2);
    // or, if you don't care about ordering
    // return span1.SequenceEqual(span2);
}
#else
// The most basic implementation, in platform-agnostic, safe C#
public static bool Compare(byte[] range1, int offset1, byte[] range2, int offset2, int count)
{
    // Working backwards lets the compiler optimize away bound checking after the first loop
    for (int i = count - 1; i >= 0; --i)
    {
        if (range1[offset1 + i] != range2[offset2 + i])
        {
            return false;
        }
    }

    return true;
}
#endif

1

Pensé en los métodos de aceleración de transferencia de bloques integrados en muchas tarjetas gráficas. Pero luego tendría que copiar todos los datos en bytes, por lo que esto no le ayuda mucho si no desea implementar una porción completa de su lógica en código no administrado y dependiente del hardware ...

Otra forma de optimización similar al enfoque que se muestra arriba sería almacenar la mayor cantidad de datos posible en un largo [] en lugar de un byte [] desde el principio, por ejemplo, si lo está leyendo secuencialmente desde un archivo binario, o si usa un archivo mapeado en memoria, lea los datos como valores largos [] o únicos largos. Luego, su ciclo de comparación solo necesitará 1/8 del número de iteraciones que tendría que hacer para un byte [] que contiene la misma cantidad de datos. Es una cuestión de cuándo y con qué frecuencia necesita comparar frente a cuándo y con qué frecuencia necesita acceder a los datos byte a byte, por ejemplo, para usarlos en una llamada API como parámetro en un método que espera un byte []. Al final, solo puede saber si realmente conoce el caso de uso ...


La respuesta aceptada vuelve a convertir el búfer de bytes como un búfer largo y lo compara como usted describe.
Hafthor

1

Es casi seguro que es mucho más lento que cualquier otra versión dada aquí, pero fue divertido de escribir.

static bool ByteArrayEquals(byte[] a1, byte[] a2) 
{
    return a1.Zip(a2, (l, r) => l == r).All(x => x);
}

1

Me decidí por una solución inspirada en el método EqualBytesLongUnrolled publicado por ArekBulski con una optimización adicional. En mi caso, las diferencias de matriz en las matrices tienden a estar cerca de la cola de las matrices. En las pruebas, descubrí que cuando este es el caso de las matrices grandes, poder comparar los elementos de la matriz en orden inverso le da a esta solución una gran ganancia de rendimiento sobre la solución basada en memcmp. Aquí está esa solución:

public enum CompareDirection { Forward, Backward }

private static unsafe bool UnsafeEquals(byte[] a, byte[] b, CompareDirection direction = CompareDirection.Forward)
{
    // returns when a and b are same array or both null
    if (a == b) return true;

    // if either is null or different lengths, can't be equal
    if (a == null || b == null || a.Length != b.Length)
        return false;

    const int UNROLLED = 16;                // count of longs 'unrolled' in optimization
    int size = sizeof(long) * UNROLLED;     // 128 bytes (min size for 'unrolled' optimization)
    int len = a.Length;
    int n = len / size;         // count of full 128 byte segments
    int r = len % size;         // count of remaining 'unoptimized' bytes

    // pin the arrays and access them via pointers
    fixed (byte* pb_a = a, pb_b = b)
    {
        if (r > 0 && direction == CompareDirection.Backward)
        {
            byte* pa = pb_a + len - 1;
            byte* pb = pb_b + len - 1;
            byte* phead = pb_a + len - r;
            while(pa >= phead)
            {
                if (*pa != *pb) return false;
                pa--;
                pb--;
            }
        }

        if (n > 0)
        {
            int nOffset = n * size;
            if (direction == CompareDirection.Forward)
            {
                long* pa = (long*)pb_a;
                long* pb = (long*)pb_b;
                long* ptail = (long*)(pb_a + nOffset);
                while (pa < ptail)
                {
                    if (*(pa + 0) != *(pb + 0) || *(pa + 1) != *(pb + 1) ||
                        *(pa + 2) != *(pb + 2) || *(pa + 3) != *(pb + 3) ||
                        *(pa + 4) != *(pb + 4) || *(pa + 5) != *(pb + 5) ||
                        *(pa + 6) != *(pb + 6) || *(pa + 7) != *(pb + 7) ||
                        *(pa + 8) != *(pb + 8) || *(pa + 9) != *(pb + 9) ||
                        *(pa + 10) != *(pb + 10) || *(pa + 11) != *(pb + 11) ||
                        *(pa + 12) != *(pb + 12) || *(pa + 13) != *(pb + 13) ||
                        *(pa + 14) != *(pb + 14) || *(pa + 15) != *(pb + 15)
                    )
                    {
                        return false;
                    }
                    pa += UNROLLED;
                    pb += UNROLLED;
                }
            }
            else
            {
                long* pa = (long*)(pb_a + nOffset);
                long* pb = (long*)(pb_b + nOffset);
                long* phead = (long*)pb_a;
                while (phead < pa)
                {
                    if (*(pa - 1) != *(pb - 1) || *(pa - 2) != *(pb - 2) ||
                        *(pa - 3) != *(pb - 3) || *(pa - 4) != *(pb - 4) ||
                        *(pa - 5) != *(pb - 5) || *(pa - 6) != *(pb - 6) ||
                        *(pa - 7) != *(pb - 7) || *(pa - 8) != *(pb - 8) ||
                        *(pa - 9) != *(pb - 9) || *(pa - 10) != *(pb - 10) ||
                        *(pa - 11) != *(pb - 11) || *(pa - 12) != *(pb - 12) ||
                        *(pa - 13) != *(pb - 13) || *(pa - 14) != *(pb - 14) ||
                        *(pa - 15) != *(pb - 15) || *(pa - 16) != *(pb - 16)
                    )
                    {
                        return false;
                    }
                    pa -= UNROLLED;
                    pb -= UNROLLED;
                }
            }
        }

        if (r > 0 && direction == CompareDirection.Forward)
        {
            byte* pa = pb_a + len - r;
            byte* pb = pb_b + len - r;
            byte* ptail = pb_a + len;
            while(pa < ptail)
            {
                if (*pa != *pb) return false;
                pa++;
                pb++;
            }
        }
    }

    return true;
}

0

Lo sentimos, si está buscando una forma administrada, ya lo está haciendo correctamente y, que yo sepa, no hay un método integrado en el BCL para hacerlo.

Debe agregar algunas comprobaciones nulas iniciales y luego reutilizarlas como si estuviera en BCL.


Tenía razón cuando escribió que, sin embargo, en 2010 (.NET 4.0) apareció un método BCL, vea la respuesta de Ohad Schneider. En el momento de la pregunta, .NET 3.5 tenía Linq (ver la respuesta de aku).
Jeppe Stig Nielsen


-2

Si está buscando un comparador de igualdad de matriz de bytes muy rápido, le sugiero que eche un vistazo a este artículo de STSdb ​​Labs: Comparador de igualdad de matriz de bytes.Cuenta con algunas de las implementaciones más rápidas para la comparación de igualdad de matriz byte [], que se presentan, se prueban y resumen el rendimiento.

También puede centrarse en estas implementaciones:

BigEndianByteArrayComparer - comparador de matriz de byte rápido [] de izquierda a derecha (BigEndian) BigEndianByteArrayEqualityComparer - - comparador de igualdad de byte rápido [] de izquierda a derecha (BigEndian) LittleEndianByteArrayComparer - byte rápido [] comparador de matriz de derecha a izquierda (LittleEndian) LittleEndianByteArrayEqualityComparer - octeto rápido [] comparador de igualdad de derecha a izquierda (LittleEndian)


-2

La respuesta corta es esta:

    public bool Compare(byte[] b1, byte[] b2)
    {
        return Encoding.ASCII.GetString(b1) == Encoding.ASCII.GetString(b2);
    }

De esta manera, puede usar la comparación de cadenas optimizada de .NET para hacer una comparación de matriz de bytes sin la necesidad de escribir código inseguro. Así es como se hace en segundo plano :

private unsafe static bool EqualsHelper(String strA, String strB)
{
    Contract.Requires(strA != null);
    Contract.Requires(strB != null);
    Contract.Requires(strA.Length == strB.Length);

    int length = strA.Length;

    fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar)
    {
        char* a = ap;
        char* b = bp;

        // Unroll the loop

        #if AMD64
            // For the AMD64 bit platform we unroll by 12 and
            // check three qwords at a time. This is less code
            // than the 32 bit case and is shorter
            // pathlength.

            while (length >= 12)
            {
                if (*(long*)a     != *(long*)b)     return false;
                if (*(long*)(a+4) != *(long*)(b+4)) return false;
                if (*(long*)(a+8) != *(long*)(b+8)) return false;
                a += 12; b += 12; length -= 12;
            }
       #else
           while (length >= 10)
           {
               if (*(int*)a != *(int*)b) return false;
               if (*(int*)(a+2) != *(int*)(b+2)) return false;
               if (*(int*)(a+4) != *(int*)(b+4)) return false;
               if (*(int*)(a+6) != *(int*)(b+6)) return false;
               if (*(int*)(a+8) != *(int*)(b+8)) return false;
               a += 10; b += 10; length -= 10;
           }
       #endif

        // This depends on the fact that the String objects are
        // always zero terminated and that the terminating zero is not included
        // in the length. For odd string sizes, the last compare will include
        // the zero terminator.
        while (length > 0)
        {
            if (*(int*)a != *(int*)b) break;
            a += 2; b += 2; length -= 2;
        }

        return (length <= 0);
    }
}

En mis pruebas, la conversión a una cadena destruye la ventaja de la comparación más rápida. Esto fue aproximadamente 2.5 veces más lento que un bucle simple.
Doug Clutter

Cuando hice lo mismo, el simple fue aproximadamente 8 veces más lento. ¿Puedes escribir tu código aquí?
Alon

1
¿Se romperá esto si un byte contiene un valor nulo (0)?
Joseph Lennox

-1 Además de ser lento debido a la conversión a cadena como lo señala @DougClutter, esto fallará si la matriz de bytes contiene datos que no son ASCII. Para obtener el resultado correcto, necesitaría usar iso-8859-1.
Joe

2
Compare(new byte[]{128}, new byte[]{ 255 }) == trueno tiene errores en absoluto ...
CodesInChaos

-2

Dado que muchas de las soluciones sofisticadas anteriores no funcionan con UWP y porque amo a Linq y los enfoques funcionales, le presento mi versión de este problema. Para escapar de la comparación cuando se produce la primera diferencia, elegí .FirstOrDefault ()

public static bool CompareByteArrays(byte[] ba0, byte[] ba1) =>
    !(ba0.Length != ba1.Length || Enumerable.Range(1,ba0.Length)
        .FirstOrDefault(n => ba0[n] != ba1[n]) > 0);

-1 porque este código está roto y aparentemente no se ha probado. Esto arroja una IndexOutOfRangeExceptional comparar las matrices no vacías porque se está accediendo a los elementos 1a través de ba0.Lengthcuando debería ser 0a través ba0.Length - 1. Si arreglas eso con Enumerable.Range(0, ba0.Length)él, aún se devuelve incorrectamente truepara matrices de igual longitud donde solo los primeros elementos difieren porque no puedes distinguir entre los primeros elementos que satisfacen predicatey ningún elemento que satisface predicate; FirstOrDefault<int>()vuelve 0en ambos casos.
BACON

La lección aquí, niños: no traigan un cuchillo a un tiroteo
Richard Hauer
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.