Comparando matrizes de dois bytes no .NET


541

Como posso fazer isso rápido?

Claro que posso fazer isso:

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

Mas estou procurando uma função BCL ou alguma maneira comprovada altamente otimizada de fazer isso.

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

funciona bem, mas não parece que isso funcionaria para x64.

Observe minha resposta super rápida aqui .


1
"Isso meio que conta com o fato de que as matrizes iniciam o qword alinhado." Isso é um grande se. Você deve corrigir o código para refletir isso.
9119 Joe Chung

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

Gostei de @OhadSchneider responder sobreIStructuralEquatable
LCJ

Respostas:


613

Você pode usar o 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

Se você não puder usar o .NET 3.5 por algum motivo, seu método está OK.
O ambiente do compilador \ tempo de execução otimizará seu loop para que você não precise se preocupar com o desempenho.


4
Mas o SequenceEqual não leva mais tempo para processar do que uma comparação insegura? Especialmente quando você está fazendo milhares de comparações?
tcables

90
Sim, isso é cerca de 50x mais lento que a comparação insegura.
Hafthor

27
Isso está realmente ressuscitando os mortos aqui, mas lento é realmente uma palavra ruim para usar aqui. 50x mais lento parece ruim, mas muitas vezes você não está comparando dados suficientes para fazer a diferença e, se estiver, precisa realmente fazer uma avaliação comparativa para seu próprio caso, por uma infinidade de razões. Por exemplo, observe que o criador da resposta não segura observa uma diferença de 7x mais lenta, em vez de 50x mais lenta (a velocidade do método não seguro também depende do alinhamento dos dados). Nos raros casos em que esses números são importantes, P / Invoke é ainda mais rápido.
Selali Adobor

4
Então a implementação mais lenta obtém mais de 300 curtidas? Eu sugeriria conectar o msvcrt.dll, pois seria a maneira mais rápida de fazer o trabalho.
TGarrett

69
O mais rápido não é a coisa mais importante para uma empresa. A capacidade de manutenção é muito "mais rápida" do que a economia nesse código será de 99% dos casos. Estou usando SequenceEqual e meu código inteiro é <1ms. Esses µs que você está salvando nunca resultarão nos 5 minutos de falta de legibilidade do P / Invoke.
PRMan

236

Os poderes P / Invoke são ativados!

[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 - isso provou ser o mais rápido de longe em bitmaps, pelo menos: stackoverflow.com/questions/2031217/…
Erik Forbes

25
A fixação não é necessária neste caso. O empacotador executa a fixação automática ao chamar código nativo com o PInvoke. Referência: stackoverflow.com/questions/2218444/…
Mark Glasgow

14
O P / Invoke pode gerar boos, mas é de longe a mais rápida de todas as soluções apresentadas, incluindo uma implementação que criei que utiliza comparações inseguras do tamanho de ponteiros. Existem algumas otimizações que você pode fazer antes de chamar o código nativo, incluindo a igualdade de referência e a comparação do primeiro e do último elementos.
Josh

38
Por que a vaia? O Poster queria uma implementação rápida e uma comparação otimizada da linguagem assembly não pode ser batida. Não sei como obter um "REPE CMPSD" do .NET sem P / INVOKE.
Jason Goemaat

14
Nitpick: MSVCR.dll não deve ser usado pelo código do usuário. Para usar o MSVCR, você precisaria distribuir o tempo de execução usando a versão que distribui. ( msdn.microsoft.com/en-us/library/… e blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.aspx )
Mitch

160

Há uma nova solução interna para isso no .NET 4 - IStructuralEquatable

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

17
De acordo com este post do blog , na verdade, é muito lento.
Matt Johnson-Pint

48
Louco devagar. Aproximadamente 180x mais lento que o simples para loop.
Hafthor

Isso funciona, mas não entendo o porquê. Um byte [] é um tipo primitivo que não implementa o IStructuralEquatable, então por que você pode convertê-lo - e um elenco implícito nisso! E então o método "Equals" da interface fica disponível magicamente ... de onde vem a implementação desse método? Alguém pode me dar uma pista?
21313 Josh

1
Por que não apenas StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2). Não NullReferenceExceptionestá aqui.
ta.speot.is

1
@ ta.speot.is Obrigado, não posso discutir com um só forro! A solução anterior foi um pouco mais eficiente, pois salvou a conversão em IStructuralEquatable (uma matriz é estaticamente conhecida por IStructuralEquatable), mas, de fato, suas sugestões fazem com que o método funcione também para argumentos nulos.
Ohad Schneider

76

O usuário Gil sugeriu um código não seguro que gerou esta solução:

// 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 faz uma comparação baseada em 64 bits para o máximo possível da matriz. Isso conta com o fato de que as matrizes iniciam o qword alinhado. Funcionará se não estiver alinhado com o qword, apenas não tão rápido como se fosse.

Ele executa cerca de sete temporizadores mais rápido que o forloop simples . O uso da biblioteca J # foi executado de forma equivalente ao forloop original . Usar .SequenceEqual é executado cerca de sete vezes mais devagar; Eu acho que só porque ele está usando IEnumerator.MoveNext. Imagino que as soluções baseadas em LINQ sejam pelo menos lentas ou piores.


3
Ótima solução. Mas uma dica (pequena): uma comparação se as referências a1 e a2 forem iguais pode acelerar as coisas se alguém fornecer a mesma matriz para a1 e b1.
mmmmmmmm

12
Novos dados de teste na versão .NET 4 x64: IStructualEquatable.equals ~ 180x mais lento, SequenceEqual 15x mais lento, hash SHA1 comparam 11x mais devagar, bitconverter ~ o mesmo, inseguro 7x mais rápido, pinvoke 11x mais rápido. Muito legal que inseguro é apenas um pouco mais lento que P / Invoke no memcmp.
precisa saber é o seguinte

3
Esse link fornece bons detalhes sobre por que o alinhamento da memória é importante ibm.com/developerworks/library/pa-dalign - portanto, uma otimização pode ser verificar o alinhamento e se as duas matrizes estiverem desalinhadas na mesma quantidade, faça comparações de bytes até que ambas sejam em um limite de qword.
Hafthor

5
isso não daria falso quando a1 e a2 são nulos?
Nawfal #

2
@CristiDiaconescu Eu repeti a resposta de KevinDriedger. O que eu provavelmente deveria fazer é disponibilizar o conjunto de testes e meus resultados no github e vincular a ele na minha resposta.
Hafthor

74

Span<T> oferece uma alternativa extremamente competitiva sem ter que lançar fluff confuso e / ou não portátil na base de código do seu próprio aplicativo:

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

A implementação (essencial) do .NET Core 3.1.0 pode ser encontrada aqui .

Eu já revisto @ essência de EliArbel para adicionar este método como SpansEqual, gota a maioria dos artistas menos interessantes em benchmarks dos outros, executá-lo com diferentes tamanhos de matriz, gráficos de saída, e marca SpansEqualcomo linha de base para que ele relata como os diferentes métodos de comparar a SpansEqual.

Os números abaixo são dos resultados, levemente editados para remover a coluna "Erro".

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

Fiquei surpreso ao ver SpansEqualnão sair por cima para os métodos de tamanho máximo de matriz, mas a diferença é tão pequena que acho que nunca importará.

Informações do meu 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

Eu nunca pensei que usaria o Span <T> ou algo próximo a ele em todas as coisas que faço. Graças a você, agora posso me gabar disso aos meus colegas de trabalho.
Jokab

SequenceEqual é especialmente implementado como um método Span? Pensei que era apenas um dos métodos de extensão IEnumerable.
Zastai 06/0418

1
@Zastai sim, {ReadOnly,}Span<T>tem sua própria versão de SequenceEqual(mesmo nome porque tem o mesmo contrato que o IEnumerable<T>método de extensão correspondente , é apenas mais rápido). Observe que {ReadOnly,}Span<T>não é possível usar IEnumerable<T>métodos de extensão devido às restrições de ref structtipos.
Joe Amenta

1
@ Sentinel, o pacote System.Memory possui implementações "portáteis" / "lentas" Span<T>para netstandard1.1cima e para cima (então brinque com este gráfico interativo para ver quais são). "Rápido" Span<T>está disponível apenas no .NET Core 2.1, no momento, mas observe que SequenceEqual<T>, especificamente, deve haver muito pouca diferença entre "rápido" e "lento" / "portátil" (embora os netstandard2.0destinos tenham uma leve melhora porque eles tem o caminho do código vetorizado).
Joe Amenta

1
install-package system.memory
Chris Moschini 16/10

30

Se você não se opuser a isso, poderá importar o assembly J # "vjslib.dll" e usar seu método Arrays.equals (byte [], byte []) ...

Não me culpe se alguém ri de você embora ...


EDIT: Por quanto pouco vale a pena, usei o Reflector para desmontar o código e aqui está o que parece:

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

O .NET 3.5 e mais recentes têm um novo tipo público, System.Data.Linq.Binaryque encapsula byte[]. Ele implementa IEquatable<Binary>que (com efeito) compara matrizes de dois bytes. Observe que System.Data.Linq.Binarytambém possui um operador de conversão implícito de byte[].

Documentação do MSDN: System.Data.Linq.Binary

Descompilador do refletor do 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;
}

O interessante é que eles só procedem ao loop de comparação byte a byte se os hashes dos dois objetos binários forem iguais. Isso, no entanto, tem o custo de calcular o hash no construtor de Binaryobjetos (atravessando a matriz com forloop :-)).

A implementação acima significa que, na pior das hipóteses, você pode ter que percorrer as matrizes três vezes: primeiro para calcular o hash da matriz1, depois para calcular o hash da matriz2 e finalmente (porque esse é o pior cenário, comprimentos e hashes iguais) para comparar bytes na matriz1 com bytes na matriz 2.

No geral, embora System.Data.Linq.Binaryesteja integrado ao BCL, não acho que seja a maneira mais rápida de comparar matrizes de dois bytes: - |.


20

Eu postei uma pergunta semelhante sobre como verificar se o byte [] está cheio de zeros. (O código SIMD foi batido, removi-o desta resposta.) Aqui está o código mais rápido das minhas comparações:

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 em duas matrizes de 256 MB de bytes:

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

1
Eu confirmo. Eu também fiz os testes. Isso é mais rápido que a resposta que usa chamada não segura do memcmp.
ujeenator

1
@AmberdeBlack Você tem certeza? Você testou com pequenas matrizes?
Zar Shardan 31/03

@ArekBulski Tem certeza de que isso é mais rápido que o memcmp, porque meus testes mostram o contrário?
Zar Shardan #

Consegui um desempenho praticamente idêntico entre este e o memcmp, portanto, +1 para uma solução totalmente gerenciada.
Mike Marynowski 26/10/19

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
É isso que eu tenho usado. Mas umm ... soa como uma comparação seqüencial que você faria usando um loop simples, portanto não muito rápido. Seria bom refletir e ver o que realmente está fazendo. A julgar pelo nome, não é nada chique.
Sergey Akopov 6/01/11

1
Sim, mas já mencionado na resposta aceita. Aliás, você pode remover a especificação de tipo lá.
Nawfal,

10

Vamos adicionar mais um!

Recentemente, a Microsoft lançou um pacote NuGet especial, System.Runtime.CompilerServices.Unsafe . É especial porque está escrito em IL e fornece funcionalidade de baixo nível que não está disponível diretamente em C #.

Um de seus métodos Unsafe.As<T>(object)permite converter qualquer tipo de referência para outro tipo de referência, ignorando as verificações de segurança. Isso geralmente é uma muito má ideia, mas se ambos os tipos têm a mesma estrutura, ele pode trabalhar. Portanto, podemos usar isso para converter um byte[]em um 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;
}

Observe que long1.Lengthainda retornaria o comprimento da matriz original, pois ela é armazenada em um campo na estrutura de memória da matriz.

Esse método não é tão rápido quanto os outros métodos demonstrados aqui, mas é muito mais rápido que o método ingênuo, não usa código inseguro ou P / Invoke ou fixação, e a implementação é bastante direta (IMO). Aqui estão alguns resultados do BenchmarkDotNet da minha 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 |

Eu também criei uma essência com todos os testes .


Ele não usa a palavra-chave inseguro, no entanto, chama o código inseguro de qualquer maneira, usando o System.Runtime.CompilerServices.Unsafe
Paulo Zemek

Eu atualizei minha NewMemCmpresposta para usar o AVX-2
Mr Anderson

8

Eu desenvolvi um método que bate ligeiramente memcmp()(resposta de plinth) e muito ligeiramente EqualBytesLongUnrolled()(resposta de Arek Bulski) no meu PC. Basicamente, ele desenrola o loop por 4 em vez de 8.

Atualização 30 de março de 2019 :

A partir do .NET core 3.0, temos suporte a SIMD!

Esta solução é mais rápida por uma margem considerável no meu 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;
}

Minhas medidas difere para .NET 462 pode o Netcore:
Motlicek Petr

O código falha ao comparar duas matrizes de comprimento 0, porque a pinagem retorna null.
perfil completo de Glenn Slayden

O memcmp não é apenas um comparador de ações. Ele fornece informações sobre qual objeto é maior ou menor. Você pode adotar seu algoritmo para esse fim e verificar o desempenho?
precisa saber é o seguinte

É mais rápido que Spane memcpy?
silkfire 9/03

@silkfire No .NET core 3 e na CPU moderna, ele deve ser 2-3 vezes mais rápido para matrizes grandes.
Anderson Anderson

6

Eu usaria código inseguro e executaria o forloop comparando ponteiros Int32.

Talvez você também deva considerar a verificação das matrizes como não nulas.


5

Se você observar como o .NET executa string.Equals, verá que ele usa um método particular chamado EqualsHelper que possui uma implementação de ponteiro "não segura". O .NET Reflector é seu amigo para ver como as coisas são feitas internamente.

Isso pode ser usado como um modelo para comparação de matriz de bytes, no qual eu fiz uma implementação na postagem do blog Comparação rápida de matriz de bytes em C # . Também fiz alguns benchmarks rudimentares para ver quando uma implementação segura é mais rápida que a insegura.

Dito isto, a menos que você realmente precise de desempenho matador, eu faria uma comparação simples do loop fr.


3

Não foi possível encontrar uma solução com a qual eu esteja completamente feliz (desempenho razoável, mas nenhum código / pinvoke inseguro), por isso vim com isso, nada realmente original, mas 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;
    }

Desempenho em comparação com algumas das outras soluções nesta página:

Loop simples: 19837 ticks, 1,00

* BitConverter: 4886 ticks, 4,06

Comparação: 1636 ticks, 12.12

EqualBytesLongUnrolled: 637 ticks, 31,09

P / Invocar memcmp: 369 ticks, 53.67

Testado no linqpad, 1000000 bytes de matrizes idênticas (pior cenário), 500 iterações cada.


Sim, observei que, no comentário de stackoverflow.com/a/1445280/4489, meus testes mostram que na verdade é um pouco mais lento que o loop for simples que tive na pergunta original.
Hafthor 30/03/16

você tem certeza? Nos meus testes é 4 vezes mais rápido? Porém, nada supera o bom e antigo código nativo, mesmo com a sobrecarga do empacotamento.
Zar Shardan 31/03

3

Parece que EqualBytesLongUnrolled é o melhor das sugestões acima.

Os métodos ignorados (Enumerable.SequenceEqual, StructuralComparisons.StructuralEqualityComparer.Equals) não eram pacientes com lentidão. Em matrizes de 265 MB, eu medi isso:

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 |

Eu atualizei minha NewMemCmpresposta para usar o AVX-2
Mr Anderson

3

Eu não vi muitas soluções linq aqui.

Não tenho certeza das implicações de desempenho, no entanto, geralmente mantenho linqcomo regra geral e otimizo mais tarde, se necessário.

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

Observe que isso só funciona se forem do mesmo tamanho de matrizes. uma extensão poderia parecer tão

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

O ponto principal da pergunta é uma solução mais rápida que a função postou na pergunta.
CodesInChaos

3

Fiz algumas medições usando o programa anexado .net 4.7 release build sem o depurador conectado. Acho que as pessoas estão usando a métrica errada, já que você se preocupa com a velocidade aqui. Quanto tempo leva para descobrir se a matriz de dois bytes é igual. ou seja, taxa de transferência em 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 você pode ver, não há maneira melhor do que memcmpe é ordens de magnitude mais rápidas. Um forloop simples é a segunda melhor opção. E ainda me surpreende por que a Microsoft não pode simplesmente incluir um 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 matrizes de bytes curtos, o seguinte é um truque interessante:

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

Provavelmente, eu cairia na solução listada na pergunta.

Seria interessante fazer uma análise de desempenho desse código.


int i = 0; for (; i <a1.Length-7; i + = 8) if (BitConverter.ToInt64 (a1, i)! = BitConverter.ToInt64 (a2, i)) retorna false; for (; i <a1.Length; i ++) if (a1 [i]! = a2 [i]) retorna false; return true; // um pouco mais lento que o simples para loop.
precisa saber é o seguinte

2

Para aqueles que se preocupam com a ordem (ou seja, desejam que você memcmpretorne intcomo deveria, em vez de nada), o .NET Core 3.0 (e presumivelmente o .NET Standard 2.1, também conhecido como .NET 5.0) incluirá um Span.SequenceCompareTo(...)método de extensão (mais a Span.SequenceEqualTo) que pode ser usado para comparar duas ReadOnlySpan<T>instâncias ( where T: IComparable<T>).

Na proposta original do GitHub , a discussão incluiu comparações de abordagens com cálculos de tabelas de salto, leitura de byte[]as long[], uso de SIMD e p / invocação para a implementação de CLR memcmp.

A partir de agora, esse deve ser o seu método principal para comparar matrizes de bytes ou intervalos de bytes (como deve ser usado em Span<byte>vez das byte[]APIs do .NET Standard 2.1) e é suficientemente rápido o suficiente para que você não precise mais otimizá-lo (e não, apesar das semelhanças no nome, ele não é tão abismal quanto o horrível 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

Pensei nos métodos de aceleração de transferência de bloco incorporados a muitas placas gráficas. Mas você teria que copiar todos os dados em bytes, para que isso não lhe ajude muito se você não quiser implementar uma parte inteira de sua lógica em código não gerenciado e dependente de hardware ...

Outra maneira de otimização semelhante à abordagem mostrada acima seria armazenar o máximo de dados possível em um longo [] em vez de um byte [] desde o início, por exemplo, se você estiver lendo sequencialmente em um arquivo binário, ou se você usar um arquivo mapeado na memória, leia os dados com valores longos [] ou únicos longos. Então, seu loop de comparação precisará apenas de 1/8 do número de iterações necessárias para um byte [] que contenha a mesma quantidade de dados. É uma questão de quando e com que frequência você precisa comparar versus quando e com que frequência precisa acessar os dados de forma a byte a byte, por exemplo, para usá-los em uma chamada de API como parâmetro em um método que espera um byte []. No final, você só pode saber se realmente conhece o caso de uso ...


A resposta aceita reformula o buffer de bytes como um buffer longo e o compara conforme você descreve.
Hafthor

1

Isso é quase certamente muito mais lento do que qualquer outra versão apresentada aqui, mas foi divertido de escrever.

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

1

Eu decidi por uma solução inspirada no método EqualBytesLongUnrolled publicado por ArekBulski com uma otimização adicional. No meu exemplo, diferenças de matriz em matrizes tendem a estar próximas à cauda das matrizes. Nos testes, descobri que, quando esse é o caso de matrizes grandes, a possibilidade de comparar os elementos da matriz na ordem inversa fornece a esta solução um enorme ganho de desempenho em relação à solução baseada em memcmp. Aqui está essa solução:

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

Desculpe, se você está procurando uma maneira gerenciada, já está fazendo isso corretamente e, pelo que sei, não há método incorporado na BCL para fazer isso.

Você deve adicionar algumas verificações nulas iniciais e apenas reutilizá-las como se estivessem na BCL.


Você estava certo quando escreveu que, no entanto, em 2010 (.NET 4.0) veio um método BCL, consulte a resposta de Ohad Schneider. No momento da pergunta, o .NET 3.5 tinha o Linq (consulte a resposta do aku).
Jeppe Stig Nielsen


-2

Se você estiver procurando por um comparador de igualdade de matriz de bytes muito rápido, sugiro que você dê uma olhada neste artigo do STSdb ​​Labs: Comparador de igualdade de matriz de bytes. Ele apresenta algumas das implementações mais rápidas para comparação de igualdade de matriz de bytes [], que são apresentadas, desempenho testado e resumido.

Você também pode se concentrar nessas implementações:

BigEndianByteArrayComparer - byte rápido [] array comparer da esquerda para a direita (bigEndian) BigEndianByteArrayEqualityComparer - - byte rápido [] igualdade comparer da esquerda para a direita (bigEndian) LittleEndianByteArrayComparer - byte rápido [] array comparer da direita para a esquerda (littleEndian) LittleEndianByteArrayEqualityComparer - byte rápido [] comparador de igualdade da direita para a esquerda (LittleEndian)


-2

A resposta curta é esta:

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

Dessa forma, você pode usar a comparação otimizada da string .NET para comparar uma matriz de bytes sem a necessidade de escrever código não seguro. É assim que é feito em 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);
    }
}

Nos meus testes, a conversão em uma string destrói a vantagem da comparação mais rápida. Isso foi cerca de 2,5 vezes mais lento que um loop simples.
Doug Clutter

Quando eu fiz o mesmo, o simples para foi cerca de 8 vezes mais lento. Você pode escrever seu código aqui?
Alon

1
Isso será interrompido se um byte contiver um valor nulo (0)?
Joseph Lennox

-1 Além de lento, devido à conversão em string, conforme apontado por @DougClutter, isso falhará se a matriz de bytes contiver dados não ASCII. Para obter o resultado certo, seria necessário usar o iso-8859-1.
Joe

2
Compare(new byte[]{128}, new byte[]{ 255 }) == truenão buggy em tudo ...
CodesInChaos

-2

Como muitas das soluções sofisticadas acima não funcionam com o UWP e porque eu amo o Linq e as abordagens funcionais, pressiono minha versão para esse problema. Para escapar da comparação quando a primeira diferença ocorre, escolhi .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 esse código está quebrado e aparentemente não testado. Isso gera uma IndexOutOfRangeExceptionao comparar matrizes não vazios porque você está acessando elementos 1através ba0.Lengthquando deveria ser 0através ba0.Length - 1. Se você corrigir isso, Enumerable.Range(0, ba0.Length)ele ainda retornará incorretamente truepara matrizes de comprimento igual, onde apenas os primeiros elementos diferem porque você não pode distinguir entre os primeiros elementos satisfatórios predicatee nenhum elemento satisfatório predicate; FirstOrDefault<int>()retorna 0em ambos os casos.
BACON

A lição aqui crianças: não trazem uma faca para um tiroteio
Richard Hauer
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.