我想我错过了更好地利用泛型或LINQ的机会.我也不喜欢我必须使用Type []作为参数,而不是将其限制为更具体的类型集(HashAlgorithm后代),我想指定类型作为参数,并让此方法执行构建,但如果我有HashAlgorithm的调用者新建实例传入,这可能会更好看?
public List<string> ComputeMultipleHashesOnFile(string filename, Type[] hashClassTypes)
{
var hashClassInstances = new List<HashAlgorithm>();
var cryptoStreams = new List<CryptoStream>();
FileStream fs = File.OpenRead(filename);
Stream cryptoStream = fs;
foreach (var hashClassType in hashClassTypes)
{
object obj = Activator.CreateInstance(hashClassType);
var cs = new CryptoStream(cryptoStream, (HashAlgorithm)obj, CryptoStreamMode.Read);
hashClassInstances.Add((HashAlgorithm)obj);
cryptoStreams.Add(cs);
cryptoStream = cs;
}
CryptoStream cs1 = cryptoStreams.Last();
byte[] scratch = new byte[1 << 16];
int bytesRead;
do { bytesRead = cs1.Read(scratch, 0, scratch.Length); }
while (bytesRead > 0);
foreach (var stream in cryptoStreams)
{
stream.Close();
}
foreach (var hashClassInstance in hashClassInstances)
{
Console.WriteLine("{0} hash = {1}", hashClassInstance.ToString(), HexStr(hashClassInstance.Hash).ToLower());
}
}
让我们从打破问题开始吧.您的要求是您需要在同一文件上计算几种不同类型的哈希值.假设您不需要实际实例化类型.从已经实例化的函数开始:
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));
}
这就是真的.我在这个最终版本中跳过了委托的“选择器”步骤,因为如果你把这一切都写成一个函数,你不需要中间步骤;之前将其作为单独函数的原因是尽可能提供灵活性,同时仍保持编译时类型安全性.在这里,我们有点抛弃它来获得terser代码的好处.
编辑:我将添加一个东西,即虽然这段代码看起来更漂亮,但它实际上泄漏了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));
}
再次,我们在这里失去了清晰度.最好先构造实例,然后使用foreach迭代它们,并返回哈希字符串.但是你问了一个Linq解决方案,所以你就是这样.
