3

我正在使用一种或多种哈希算法对文件进行哈希处理。当我尝试参数化我想要的哈希类型时,它比我希望的要混乱得多。

我想我错过了更好地利用泛型或 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());
            }
        }
4

4 回答 4

4

为什么您提供类型Types并创建它们,而不是只允许用户传递 的实例HashAlgorithm?似乎这将完全缓解这个问题。

如果这是一个要求,那么你所拥有的确实是唯一的解决方案,因为你不能在泛型类型或函数上指定可变数量的类型参数(看起来你需要,因为它现在是一个数组) ,并且您不能强制传入的类型属于特定继承行(就像您可以强制整数参数介于 1 和 10 之间一样)。这种验证只能在运行时完成。

于 2010-03-09T00:39:17.420 回答
1

这里只是一个小问题,没有什么突破性的。每当您遍历列表时,您都可以使用 linq。对于一个班轮来说特别好:

cryptoStreams.ForEach(s => s.Close());
hashClassInstances.ForEach(h => CW("{0} ...", h.ToString()...);
于 2010-03-09T00:45:05.637 回答
1

这样的事情呢?

    public string ComputeMultipleHashesOnFile<T>(string filename, T hashClassType)
        where T : HashAlgorithm
    {

    }

where 子句将 T 参数限制为 HashAlgorithm 类型。因此,您可以创建一个继承自 HashAlgorithm 的类并实现抽象类成员:

public class HA : HashAlgorithm
{
    protected override void HashCore(byte[] array, int ibStart, int cbSize)
    {
        throw new NotImplementedException();
    }

    protected override byte[] HashFinal()
    {
        throw new NotImplementedException();
    }

    public override void Initialize()
    {
        throw new NotImplementedException();
    }
}
于 2010-03-09T00:55:40.273 回答
1

让我们从分解问题开始。您的要求是您需要在同一个文件上计算几种不同类型的哈希值。暂时假设您不需要实际实例化类型。从已经实例化它们的函数开始:

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

于 2010-03-09T01:39:18.110 回答