Come confrontare velocemente 2 file usando .NET?

Gli approcci tipici raccomandano la lettura del file binario tramite FileStream e il confronto byte per byte.

  • Un confronto di checksum come CRC sarebbe più veloce?
  • Esistono librerie .NET in grado di generare un checksum per un file?

Un confronto con il risultato sarà molto più lento di un confronto byte per byte.

Per generare un checksum, è necessario caricare ciascun byte del file ed eseguire l’elaborazione su di esso. Dovrai quindi farlo sul secondo file. L’elaborazione sarà quasi sicuramente più lenta del controllo di comparazione.

Per quanto riguarda la generazione di un checksum: puoi farlo facilmente con le classi di crittografia. Ecco un breve esempio di generazione di un checksum MD5 con C #.

Tuttavia, un checksum può essere più veloce e ha più senso se è ansible pre-calcolare il checksum del caso “test” o “base”. Se hai un file esistente e stai verificando se un nuovo file è uguale a quello esistente, pre-calcolare il checksum sul tuo file “esistente” significherebbe solo dover eseguire il DiskIO una volta, sul nuovo file. Questo probabilmente sarebbe più veloce di un confronto byte per byte.

Il metodo più lento ansible consiste nel confrontare due file byte per byte. Il più veloce che sono riuscito a ottenere è un confronto simile, ma invece di un byte alla volta, si utilizzerà una serie di byte di dimensioni Int64 e quindi si confrontano i numeri risultanti.

Ecco cosa mi è venuto in mente:

const int BYTES_TO_READ = sizeof(Int64); static bool FilesAreEqual(FileInfo first, FileInfo second) { if (first.Length != second.Length) return false; if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase)) return true; int iterations = (int)Math.Ceiling((double)first.Length / BYTES_TO_READ); using (FileStream fs1 = first.OpenRead()) using (FileStream fs2 = second.OpenRead()) { byte[] one = new byte[BYTES_TO_READ]; byte[] two = new byte[BYTES_TO_READ]; for (int i = 0; i < iterations; i++) { fs1.Read(one, 0, BYTES_TO_READ); fs2.Read(two, 0, BYTES_TO_READ); if (BitConverter.ToInt64(one,0) != BitConverter.ToInt64(two,0)) return false; } } return true; } 

Nel mio test, sono riuscito a vedere questo sovraperformare uno scenario ReadByte () diretto di quasi 3: 1. Con una media di oltre 1000 esecuzioni, ho ottenuto questo metodo a 1063 ms e il metodo seguente (confronto diretto byte per byte) a 3031 ms. L'hashing tornava sempre sotto il secondo a circa una media di 865ms. Questo test era con un file video da ~ 100 MB.

Ecco i metodi ReadByte e hashing che ho utilizzato, a scopo di confronto:

  static bool FilesAreEqual_OneByte(FileInfo first, FileInfo second) { if (first.Length != second.Length) return false; if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase)) return true; using (FileStream fs1 = first.OpenRead()) using (FileStream fs2 = second.OpenRead()) { for (int i = 0; i < first.Length; i++) { if (fs1.ReadByte() != fs2.ReadByte()) return false; } } return true; } static bool FilesAreEqual_Hash(FileInfo first, FileInfo second) { byte[] firstHash = MD5.Create().ComputeHash(first.OpenRead()); byte[] secondHash = MD5.Create().ComputeHash(second.OpenRead()); for (int i=0; i 

Oltre alla risposta di Reed Copsey :

  • Il caso peggiore è dove i due file sono identici. In questo caso è meglio confrontare i file byte per byte.

  • Se i due file non sono identici, puoi accelerare un po ‘le cose rilevando prima che non sono identici.

Ad esempio, se i due file hanno una lunghezza diversa, sai che non possono essere identici e non devi nemmeno confrontare i loro contenuti effettivi.

Se decidi che hai davvero bisogno di un confronto byte per byte completo (vedi altre risposte per la discussione sull’hashing), allora la soluzione a una riga è:

 bool bFilesAreEqual = File.ReadAllBytes(path1).SequenceEqual(File.ReadAllBytes(path2)); 

A differenza di altre risposte postate, questo funziona correttamente per qualsiasi tipo di file: binario, testo, media, eseguibile, ecc., Ma come confronto binario completo , i file che differiscono solo in modi “non importanti” (come BOM , line- terminando , codifica dei caratteri , metadati dei media, spazio bianco, padding, commenti al codice sorgente, ecc.) saranno sempre considerati non uguali .

Questo codice carica completamente entrambi i file in memoria, quindi non dovrebbe essere usato per confrontare file giganteschi . A parte questa considerazione, il caricamento completo non è realmente una penalità; infatti, questa potrebbe essere una soluzione .NET ottimale per dimensioni di file che dovrebbero essere inferiori a 85 K , poiché le piccole allocazioni in .NET sono molto economiche e deleghiamo al massimo le prestazioni e l’ottimizzazione dei file a CLR / BCL .

Inoltre, per tali scenari di lavoro, i dubbi sulle prestazioni del confronto byte per byte tramite gli enumeratori LINQ (come mostrato qui) sono discutibili, dal momento che colpire il disco per l’I / O di file ridurrà, di diversi ordini di grandezza, i vantaggi di le varie alternative di confronto della memoria. Ad esempio, anche se SequenceEqual fornisce in effetti “l’ottimizzazione” dell’abbandono al primo disallineamento , questo non ha molta importanza dopo aver già recuperato il contenuto dei file, ognuno dei quali è necessario per confermare la corrispondenza.

D’altra parte, il codice sopra non include l’interruzione di fame per file di dimensioni differenti , che possono fornire una differenza di prestazioni tangibile (possibilmente misurabile). Questo è tangibile perché, mentre la lunghezza del file è disponibile nella struttura WIN32_FILE_ATTRIBUTE_DATA (che deve comunque essere recuperata prima per qualsiasi accesso ai file), continuare ad accedere ai contenuti del file richiede un recupero completamente diverso che potrebbe essere evitato. Se sei preoccupato per questo, la soluzione diventa due righe:

  // slight optimization over the code shown above bool bFilesAreEqual = new FileInfo(path1).Length == new FileInfo(path2).Length && File.ReadAllBytes(path1).SequenceEqual(File.ReadAllBytes(path2)); 

Si potrebbe anche estendere questo per evitare i recuperi secondari se i valori di Length (equivalente) sono entrambi trovati a zero (non mostrati) e / o per evitare di build ogni FileInfo due volte (anche non mostrato).

L’unica cosa che potrebbe rendere un confronto di checksum leggermente più veloce di un confronto byte per byte è il fatto che stai leggendo un file alla volta, riducendo in qualche modo il tempo di ricerca per la testina del disco. Questo leggero guadagno può tuttavia essere benissimo consumato dal tempo aggiunto di calcolare l’hash.

Inoltre, un confronto del checksum ovviamente ha solo la possibilità di essere più veloce se i file sono identici. Se non lo sono, un confronto byte per byte terminerebbe alla prima differenza, rendendolo molto più veloce.

Dovresti anche considerare che un confronto con il codice hash ti dice solo che è molto probabile che i file siano identici. Per essere sicuro al 100% è necessario fare un confronto byte per byte.

Se il codice hash per esempio è di 32 bit, si è certi del 99,9999999% circa che i file sono identici se i codici hash corrispondono. Questo è vicino al 100%, ma se hai veramente bisogno del 100% di certezza, non è così.

Sta diventando ancora più veloce se non leggi in piccoli pezzi da 8 byte ma fai un giro, leggendo un pezzo più grande. Ho ridotto il tempo di confronto medio a 1/4.

  public static bool FilesContentsAreEqual(FileInfo fileInfo1, FileInfo fileInfo2) { bool result; if (fileInfo1.Length != fileInfo2.Length) { result = false; } else { using (var file1 = fileInfo1.OpenRead()) { using (var file2 = fileInfo2.OpenRead()) { result = StreamsContentsAreEqual(file1, file2); } } } return result; } private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2) { const int bufferSize = 1024 * sizeof(Int64); var buffer1 = new byte[bufferSize]; var buffer2 = new byte[bufferSize]; while (true) { int count1 = stream1.Read(buffer1, 0, bufferSize); int count2 = stream2.Read(buffer2, 0, bufferSize); if (count1 != count2) { return false; } if (count1 == 0) { return true; } int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64)); for (int i = 0; i < iterations; i++) { if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64))) { return false; } } } } } 

Modifica: questo metodo non funzionerebbe per confrontare i file binari!

In. NET 4.0, la class File ha i seguenti due nuovi metodi:

 public static IEnumerable ReadLines(string path) public static IEnumerable ReadLines(string path, Encoding encoding) 

Il che significa che puoi usare:

 bool same = File.ReadLines(path1).SequenceEqual(File.ReadLines(path2)); 

Onestamente, penso che sia necessario ridurre il più ansible l’albero di ricerca.

Cose da verificare prima di passare byte per byte:

  1. Le dimensioni sono le stesse?
  2. L’ultimo byte nel file A è diverso dal file B

Inoltre, la lettura di grandi blocchi alla volta sarà più efficiente poiché le unità leggono i byte sequenziali più rapidamente. Il passaggio byte per byte causa non solo molte più chiamate di sistema, ma fa sì che la testina di lettura di un disco rigido tradizionale cerchi avanti e indietro più spesso se entrambi i file si trovano sulla stessa unità.

Leggere il chunk A e il chunk B in un buffer di byte e confrontarli (NON usare Array.Equals, vedere i commenti). Metti a punto le dimensioni dei blocchi fino a quando non colpisci ciò che ritieni sia un buon compromesso tra memoria e prestazioni. Puoi anche eseguire il multi-thread del confronto, ma non eseguire il multi-thread delle letture del disco.

I miei esperimenti dimostrano che è sicuramente utile chiamare Stream.ReadByte () meno volte, ma usare BitConverter per impacchettare i byte non fa molta differenza rispetto al confronto dei byte in un array di byte.

Quindi è ansible sostituire il ciclo “Math.Ceiling and iterations” nel commento sopra con il più semplice:

  for (int i = 0; i < count1; i++) { if (buffer1[i] != buffer2[i]) return false; } 

Immagino che abbia a che fare con il fatto che BitConverter.ToInt64 ha bisogno di fare un po 'di lavoro (controllare gli argomenti e quindi eseguire il bit shifting) prima di confrontarli e questo finisce per avere la stessa quantità di lavoro di confrontare 8 byte in due array .

Un altro miglioramento su file di grandi dimensioni con lunghezza identica potrebbe essere quello di non leggere i file in modo sequenziale, ma piuttosto confrontare blocchi più o meno casuali.

È ansible utilizzare più thread, iniziando su diverse posizioni nel file e confrontando in avanti o indietro.

In questo modo è ansible rilevare le modifiche a metà / fine del file, più rapidamente di quanto si otterrebbe utilizzando un approccio sequenziale.

Se hai solo bisogno di confrontare due file, suppongo che il modo più veloce sarebbe (in C, non so se è applicabile a .NET)

  1. apri entrambi i file f1, f2
  2. ottenere la lunghezza del file corrispondente l1, l2
  3. se l1! = l2 i file sono diversi; Stop
  4. mmap () entrambi i file
  5. usa memcmp () sui file mmap () ed

OTOH, se hai bisogno di trovare se ci sono file duplicati in un set di file N, allora il modo più veloce è senza dubbio l’uso di un hash per evitare confronti N-way bit per bit.

Qualcosa (si spera) ragionevolmente efficiente:

 public class FileCompare { public static bool FilesEqual(string fileName1, string fileName2) { return FilesEqual(new FileInfo(fileName1), new FileInfo(fileName2)); } ///  /// ///  ///  ///  /// 8kb seemed like a good default ///  public static bool FilesEqual(FileInfo file1, FileInfo file2, int bufferSize = 8192) { if (!file1.Exists || !file2.Exists || file1.Length != file2.Length) return false; var buffer1 = new byte[bufferSize]; var buffer2 = new byte[bufferSize]; using (var stream1 = file1.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) { using (var stream2 = file2.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) { while (true) { var bytesRead1 = stream1.Read(buffer1, 0, bufferSize); var bytesRead2 = stream2.Read(buffer2, 0, bufferSize); if (bytesRead1 != bytesRead2) return false; if (bytesRead1 == 0) return true; if (!ArraysEqual(buffer1, buffer2, bytesRead1)) return false; } } } } ///  /// ///  ///  ///  ///  0 means compare entire arrays ///  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; } } 

Ecco alcune funzioni di utilità che consentono di determinare se due file (o due flussi) contengono dati identici.

Ho fornito una versione “veloce” che è multi-thread in quanto confronta array di byte (ogni buffer riempito da ciò che è stato letto in ogni file) in thread diversi usando Task.

Come previsto, è molto più veloce (circa 3 volte più veloce) ma consuma più CPU (perché è multi-thread) e più memoria (perché richiede due buffer di array di byte per thread di confronto).

  public static bool AreFilesIdenticalFast(string path1, string path2) { return AreFilesIdentical(path1, path2, AreStreamsIdenticalFast); } public static bool AreFilesIdentical(string path1, string path2) { return AreFilesIdentical(path1, path2, AreStreamsIdentical); } public static bool AreFilesIdentical(string path1, string path2, Func areStreamsIdentical) { if (path1 == null) throw new ArgumentNullException(nameof(path1)); if (path2 == null) throw new ArgumentNullException(nameof(path2)); if (areStreamsIdentical == null) throw new ArgumentNullException(nameof(path2)); if (!File.Exists(path1) || !File.Exists(path2)) return false; using (var thisFile = new FileStream(path1, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { using (var valueFile = new FileStream(path2, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { if (valueFile.Length != thisFile.Length) return false; if (!areStreamsIdentical(thisFile, valueFile)) return false; } } return true; } public static bool AreStreamsIdenticalFast(Stream stream1, Stream stream2) { if (stream1 == null) throw new ArgumentNullException(nameof(stream1)); if (stream2 == null) throw new ArgumentNullException(nameof(stream2)); const int bufsize = 80000; // 80000 is below LOH (85000) var tasks = new List>(); do { // consumes more memory (two buffers for each tasks) var buffer1 = new byte[bufsize]; var buffer2 = new byte[bufsize]; int read1 = stream1.Read(buffer1, 0, buffer1.Length); if (read1 == 0) { int read3 = stream2.Read(buffer2, 0, 1); if (read3 != 0) // not eof return false; break; } // both stream read could return different counts int read2 = 0; do { int read3 = stream2.Read(buffer2, read2, read1 - read2); if (read3 == 0) return false; read2 += read3; } while (read2 < read1); // consumes more cpu var task = Task.Run(() => { return IsSame(buffer1, buffer2); }); tasks.Add(task); } while (true); Task.WaitAll(tasks.ToArray()); return !tasks.Any(t => !t.Result); } public static bool AreStreamsIdentical(Stream stream1, Stream stream2) { if (stream1 == null) throw new ArgumentNullException(nameof(stream1)); if (stream2 == null) throw new ArgumentNullException(nameof(stream2)); const int bufsize = 80000; // 80000 is below LOH (85000) var buffer1 = new byte[bufsize]; var buffer2 = new byte[bufsize]; var tasks = new List>(); do { int read1 = stream1.Read(buffer1, 0, buffer1.Length); if (read1 == 0) return stream2.Read(buffer2, 0, 1) == 0; // check not eof // both stream read could return different counts int read2 = 0; do { int read3 = stream2.Read(buffer2, read2, read1 - read2); if (read3 == 0) return false; read2 += read3; } while (read2 < read1); if (!IsSame(buffer1, buffer2)) return false; } while (true); } public static bool IsSame(byte[] bytes1, byte[] bytes2) { if (bytes1 == null) throw new ArgumentNullException(nameof(bytes1)); if (bytes2 == null) throw new ArgumentNullException(nameof(bytes2)); if (bytes1.Length != bytes2.Length) return false; for (int i = 0; i < bytes1.Length; i++) { if (bytes1[i] != bytes2[i]) return false; } return true; } 

Se i file non sono troppo grandi, puoi usare:

 public static byte[] ComputeFileHash(string fileName) { using (var stream = File.OpenRead(fileName)) return System.Security.Cryptography.MD5.Create().ComputeHash(stream); } 

Sarà ansible confrontare gli hash solo se gli hash sono utili per l’archiviazione.

(Modifica il codice per qualcosa di molto più pulito.)

Penso che ci siano applicazioni in cui “hash” è più veloce rispetto al confronto byte per byte. Se è necessario confrontare un file con altri o avere una miniatura di una foto che può cambiare. Dipende da dove e come sta usando.

 private bool CompareFilesByte(string file1, string file2) { using (var fs1 = new FileStream(file1, FileMode.Open)) using (var fs2 = new FileStream(file2, FileMode.Open)) { if (fs1.Length != fs2.Length) return false; int b1, b2; do { b1 = fs1.ReadByte(); b2 = fs2.ReadByte(); if (b1 != b2 || b1 < 0) return false; } while (b1 >= 0); } return true; } private string HashFile(string file) { using (var fs = new FileStream(file, FileMode.Open)) using (var reader = new BinaryReader(fs)) { var hash = new SHA512CryptoServiceProvider(); hash.ComputeHash(reader.ReadBytes((int)file.Length)); return Convert.ToBase64String(hash.Hash); } } private bool CompareFilesWithHash(string file1, string file2) { var str1 = HashFile(file1); var str2 = HashFile(file2); return str1 == str2; } 

Qui puoi ottenere ciò che è il più veloce.

 var sw = new Stopwatch(); sw.Start(); var compare1 = CompareFilesWithHash(receiveLogPath, logPath); sw.Stop(); Debug.WriteLine(string.Format("Compare using Hash {0}", sw.ElapsedTicks)); sw.Reset(); sw.Start(); var compare2 = CompareFilesByte(receiveLogPath, logPath); sw.Stop(); Debug.WriteLine(string.Format("Compare byte-byte {0}", sw.ElapsedTicks)); 

Facoltativamente, possiamo salvare l’hash in un database.

Spero che questo possa aiutare

Ancora un’altra risposta, derivata da @chsh. MD5 con usanze e scorciatoie per lo stesso file, il file non esiste e lunghezze diverse:

 ///  /// Performs an md5 on the content of both files and returns true if /// they match ///  /// first file /// second file /// true if the contents of the two files is the same, false otherwise public static bool IsSameContent(string file1, string file2) { if (file1 == file2) return true; FileInfo file1Info = new FileInfo(file1); FileInfo file2Info = new FileInfo(file2); if (!file1Info.Exists && !file2Info.Exists) return true; if (!file1Info.Exists && file2Info.Exists) return false; if (file1Info.Exists && !file2Info.Exists) return false; if (file1Info.Length != file2Info.Length) return false; using (FileStream file1Stream = file1Info.OpenRead()) using (FileStream file2Stream = file2Info.OpenRead()) { byte[] firstHash = MD5.Create().ComputeHash(file1Stream); byte[] secondHash = MD5.Create().ComputeHash(file2Stream); for (int i = 0; i < firstHash.Length; i++) { if (i>=secondHash.Length||firstHash[i] != secondHash[i]) return false; } return true; } } 

La mia risposta è un derivato di @lars ma corregge il bug nella chiamata a Stream.Read . Aggiungo anche un controllo rapido dei percorsi che hanno avuto altre risposte e la convalida dell’input. In breve, questa dovrebbe essere la risposta:

 using System; using System.IO; namespace ConsoleApp4 { class Program { static void Main(string[] args) { var fi1 = new FileInfo(args[0]); var fi2 = new FileInfo(args[1]); Console.WriteLine(FilesContentsAreEqual(fi1, fi2)); } public static bool FilesContentsAreEqual(FileInfo fileInfo1, FileInfo fileInfo2) { if (fileInfo1 == null) { throw new ArgumentNullException(nameof(fileInfo1)); } if (fileInfo2 == null) { throw new ArgumentNullException(nameof(fileInfo2)); } if (string.Equals(fileInfo1.FullName, fileInfo2.FullName, StringComparison.OrdinalIgnoreCase)) { return true; } if (fileInfo1.Length != fileInfo2.Length) { return false; } else { using (var file1 = fileInfo1.OpenRead()) { using (var file2 = fileInfo2.OpenRead()) { return StreamsContentsAreEqual(file1, file2); } } } } private static int ReadFullBuffer(Stream stream, byte[] buffer) { int bytesRead = 0; while (bytesRead < buffer.Length) { int read = stream.Read(buffer, bytesRead, buffer.Length - bytesRead); if (read == 0) { // Reached end of stream. return bytesRead; } bytesRead += read; } return bytesRead; } private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2) { const int bufferSize = 1024 * sizeof(Int64); var buffer1 = new byte[bufferSize]; var buffer2 = new byte[bufferSize]; while (true) { int count1 = ReadFullBuffer(stream1, buffer1); int count2 = ReadFullBuffer(stream2, buffer2); if (count1 != count2) { return false; } if (count1 == 0) { return true; } int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64)); for (int i = 0; i < iterations; i++) { if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64))) { return false; } } } } } } 

O se vuoi essere super-fantastico, puoi usare la variante asincrona:

 using System; using System.IO; using System.Threading.Tasks; namespace ConsoleApp4 { class Program { static void Main(string[] args) { var fi1 = new FileInfo(args[0]); var fi2 = new FileInfo(args[1]); Console.WriteLine(FilesContentsAreEqualAsync(fi1, fi2).GetAwaiter().GetResult()); } public static async Task FilesContentsAreEqualAsync(FileInfo fileInfo1, FileInfo fileInfo2) { if (fileInfo1 == null) { throw new ArgumentNullException(nameof(fileInfo1)); } if (fileInfo2 == null) { throw new ArgumentNullException(nameof(fileInfo2)); } if (string.Equals(fileInfo1.FullName, fileInfo2.FullName, StringComparison.OrdinalIgnoreCase)) { return true; } if (fileInfo1.Length != fileInfo2.Length) { return false; } else { using (var file1 = fileInfo1.OpenRead()) { using (var file2 = fileInfo2.OpenRead()) { return await StreamsContentsAreEqualAsync(file1, file2).ConfigureAwait(false); } } } } private static async Task ReadFullBufferAsync(Stream stream, byte[] buffer) { int bytesRead = 0; while (bytesRead < buffer.Length) { int read = await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead).ConfigureAwait(false); if (read == 0) { // Reached end of stream. return bytesRead; } bytesRead += read; } return bytesRead; } private static async Task StreamsContentsAreEqualAsync(Stream stream1, Stream stream2) { const int bufferSize = 1024 * sizeof(Int64); var buffer1 = new byte[bufferSize]; var buffer2 = new byte[bufferSize]; while (true) { int count1 = await ReadFullBufferAsync(stream1, buffer1).ConfigureAwait(false); int count2 = await ReadFullBufferAsync(stream2, buffer2).ConfigureAwait(false); if (count1 != count2) { return false; } if (count1 == 0) { return true; } int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64)); for (int i = 0; i < iterations; i++) { if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64))) { return false; } } } } } } 

Ciò che ho trovato funziona bene confrontando prima la lunghezza senza leggere i dati e quindi confrontando la sequenza di byte letti

 private static bool IsFileIdentical(string a, string b) { if (new FileInfo(a).Length != new FileInfo(b).Length) return false; return (File.ReadAllBytes(a).SequenceEqual(File.ReadAllBytes(b))); }