让我们从分解问题开始。您的要求是您需要在同一个文件上计算几种不同类型的哈希值。暂时假设您不需要实际实例化类型。从已经实例化它们的函数开始:
public IEnumerable<string> GetHashStrings(string fileName,
IEnumerable<HashAlgorithm> algorithms)
{
byte[] fileBytes = File.ReadAllBytes(fileName);
return algorithms
.Select(a => a.ComputeHash(fileBytes))
.Select(b => HexStr(b));
}
那很简单。如果文件可能很大并且您需要对其进行流式传输(请记住,这在 I/O 方面会更昂贵,只是内存更便宜),您也可以这样做,只是稍微冗长一点:
public IEnumerable<string> GetStreamedHashStrings(string fileName,
IEnumerable<HashAlgorithm> algorithms)
{
using (Stream fileStream = File.OpenRead(fileName))
{
return algorithms
.Select(a => {
fileStream.Position = 0;
return a.ComputeHash(fileStream);
})
.Select(b => HexStr(b));
}
}
这有点粗糙,在第二种情况下,Linq-ified 版本是否比普通foreach
循环更好,这是非常值得怀疑的,但是嘿,我们玩得很开心,对吧?
现在我们已经解开了散列生成代码,首先实例化它们并没有那么困难。同样,我们将从干净的代码开始——使用委托而不是类型的代码:
public IEnumerable<string> GetHashStrings(string fileName,
params Func<HashAlgorithm>[] algorithmSelectors)
{
if (algorithmSelectors == null)
return Enumerable.Empty<string>();
var algorithms = algorithmSelectors.Select(s => s());
return GetHashStrings(fileName, algorithms);
}
现在这更好了,好处是它允许在方法中实例化算法,但不需要它。我们可以像这样调用它:
var hashes = GetHashStrings(fileName,
() => new MD5CryptoServiceProvider(),
() => new SHA1CryptoServiceProvider());
如果我们真的非常迫切需要从实际Type
实例开始,我会尽量不这样做,因为它会破坏编译时类型检查,那么我们可以将其作为最后一步:
public IEnumerable<string> GetHashStrings(string fileName,
params Type[] algorithmTypes)
{
if (algorithmTypes == null)
return Enumerable.Empty<string>();
var algorithmSelectors = algorithmTypes
.Where(t => t.IsSubclassOf(typeof(HashAlgorithm)))
.Select(t => (Func<HashAlgorithm>)(() =>
(HashAlgorithm)Activator.CreateInstance(t)))
.ToArray();
return GetHashStrings(fileName, algorithmSelectors);
}
就是这样。现在我们可以运行这个(坏的)代码:
var hashes = GetHashStrings(fileName, typeof(MD5CryptoServiceProvider),
typeof(SHA1CryptoServiceProvider));
归根结底,这似乎是更多的代码,但这仅仅是因为我们以一种易于测试和维护的方式有效地组合了解决方案。如果我们想在一个 Linq 表达式中完成这一切,我们可以:
public IEnumerable<string> GetHashStrings(string fileName,
params Type[] algorithmTypes)
{
if (algorithmTypes == null)
return Enumerable.Empty<string>();
byte[] fileBytes = File.ReadAllBytes(fileName);
return algorithmTypes
.Where(t => t.IsSubclassOf(typeof(HashAlgorithm)))
.Select(t => (HashAlgorithm)Activator.CreateInstance(t))
.Select(a => a.ComputeHash(fileBytes))
.Select(b => HexStr(b));
}
这就是它的全部内容。在这个最终版本中,我跳过了委派的“选择器”步骤,因为如果你把这一切都写成一个函数,你就不需要中间步骤;早先将其作为单独函数的原因是在保持编译时类型安全的同时提供尽可能多的灵活性。在这里,我们已经把它扔掉了,以获得简洁代码的好处。
编辑:我将添加一件事,即虽然这段代码看起来更漂亮,但它实际上泄漏了HashAlgorithm
后代使用的非托管资源。你真的需要做这样的事情:
public IEnumerable<string> GetHashStrings(string fileName,
params Type[] algorithmTypes)
{
if (algorithmTypes == null)
return Enumerable.Empty<string>();
byte[] fileBytes = File.ReadAllBytes(fileName);
return algorithmTypes
.Where(t => t.IsSubclassOf(typeof(HashAlgorithm)))
.Select(t => (HashAlgorithm)Activator.CreateInstance(t))
.Select(a => {
byte[] result = a.ComputeHash(fileBytes);
a.Dispose();
return result;
})
.Select(b => HexStr(b));
}
And again we're kind of losing clarity here. It might be better to just construct the instances first, then iterate through them with foreach
and yield return
the hash strings. But you asked for a Linq solution, so there you are. ;)