我散列文件与一个或多个散列算法。当我试图参数化我想要的哈希类型,它得到了很多混乱比我希望。

我想我错过了机会,以更好地使用泛型或LINQ的。我也不喜欢,我必须使用类型[]作为参数,而不是将其限制在一个更具体的集合类型(后人的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));
}

这是容易的。如果这些文件可能是大的,你需要流呢(请记住,这将是在我而言更加昂贵/ 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指明分数的版本是否是比任何一个普通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));
}

和再次我们那种这里失去清晰度。这可能是最好只建实例,然后再通过他们与foreach迭代和yield return哈希字符串。但是,你问一个LINQ的解决方案,你就在那儿。 ;)

其他提示

为什么要提供的类型,Types和创造它们,而不是仅仅允许用户在HashAlgorithm的情况下通过了吗?这似乎是,将完全缓解该问题。

如果这是一个要求,那你有什么是真正的唯一的解决办法,因为你不能在一个泛型类型或功能(这好像你需要指定一个可变数量的类型参数,因为它是一个阵列现在),并且无法执行传递是一个特定的继承线(任何超过您可以实现的整数参数是1和10)之间的类型。那种验证只能在运行时完成。

这里只是一个小点,没有破土动工。每当你的foreach一个列表,你可以使用LINQ。它是一个内衬特别好:

cryptoStreams.ForEach(s => s.Close());
hashClassInstances.ForEach(h => CW("{0} ...", h.ToString()...);

关于这样的事情是什么?

    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();
    }
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top