Here's a solution that uses streaming to avoid memory and latency issues.
By default the file paths are included in the hashing, which will factor not only the data in the files, but the file system entries themselves, which avoids hash collisions. This post is tagged security, so this ought to be important.
Finally, this solution puts you in control of the hashing algorithm, which files get hashed, and in what order.
public static class HashAlgorithmExtensions
{
public static async Task<byte[]> ComputeHashAsync(this HashAlgorithm alg, IEnumerable<FileInfo> files, bool includePaths = true)
{
using (var cs = new CryptoStream(Stream.Null, alg, CryptoStreamMode.Write))
{
foreach (var file in files)
{
if (includePaths)
{
var pathBytes = Encoding.UTF8.GetBytes(file.FullName);
cs.Write(pathBytes, 0, pathBytes.Length);
}
using (var fs = file.OpenRead())
await fs.CopyToAsync(cs);
}
cs.FlushFinalBlock();
}
return alg.Hash;
}
}
An example that hashes all the files in a folder:
async Task<byte[]> HashFolder(DirectoryInfo folder, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
using(var alg = MD5.Create())
return await alg.ComputeHashAsync(folder.EnumerateFiles(searchPattern, searchOption));
}