实例化参数化类型列表,更好地利用泛型和Linq

时间:2010-03-09 00:35:31

标签: c# generics types covariance contravariance

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

我想我错过了更好地利用泛型或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 个答案:

答案 0 :(得分:4)

为什么要提供Types类型并创建它们而不仅仅是允许用户传入HashAlgorithm的实例?这似乎可以完全缓解这个问题。

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

答案 1 :(得分:1)

这里只是一个小问题,没有什么突破性的。每当你对列表进行操作时,都可以使用linq。这对于一个衬垫特别好:

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

答案 2 :(得分: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();
    }
}

答案 3 :(得分: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方面要贵得多,只需要更便宜的内存),您也可以这样做,它只是更冗长一点:< / p>

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

我们再一次在这里失去清晰度。最好先构造实例,然后使用foreachyield return哈希字符串迭代它们。但是你问了一个Linq解决方案,所以你就是这样。 ;)