代码之家  ›  专栏  ›  技术社区  ›  DanO Roman

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

  •  3
  • DanO Roman  · 技术社区  · 16 年前

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

    我想我错过了一个更好地利用仿制药或LINQ的机会。我也不喜欢将类型[]用作参数,而不是将其限制为更具体的类型集(hashAlgorithm后代),我希望将类型指定为参数,并让此方法进行构造,但如果调用方在instanc上新建了一个要传入的哈希算法的es?

    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 回复  |  直到 8 年前
        1
  •  1
  •   Aaronaught    16 年前

    我们先把问题分解一下。您需要在同一个文件上计算几种不同类型的散列值。暂时假设你 不要 需要实际实例化类型。从已经实例化它们的函数开始:

    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化版本是否比普通版本更好 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));
    }
    

    这就是一切。在这个最终版本中,我跳过了委托的“selector”步骤,因为如果您将所有这些都作为一个函数来编写,那么就不需要中间步骤;之前将它作为单独的函数来编写的原因是在保持编译时类型安全的同时,尽可能地提供灵活性。TY。在这里,我们为了得到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));
    }
    

    我们再一次失去了清晰的认识。最好先构造实例,然后用 前臂 和 yield return 哈希字符串。但是你要求一个LINQ解决方案,所以你来了。;)

        2
  •  4
  •   Adam Robinson    16 年前

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

    如果这是一个需求,那么您真正拥有的是唯一的解决方案,因为您不能在一个泛型类型或函数上指定一个数量可变的类型参数(这似乎是您需要的,因为它现在是一个数组),并且您不能强制将传入的类型作为一个特定的继承类型行(任何超过您所能强制的整数参数在1到10之间的值)。这种验证只能在运行时进行。

        3
  •  1
  •   Joel    16 年前

    只是一个小问题,没有什么突破。每当您前移到一个列表上时,都可以使用LINQ。它特别适合一行:

    cryptoStreams.ForEach(s => s.Close());
    hashClassInstances.ForEach(h => CW("{0} ...", h.ToString()...);
    
        4
  •  1
  •   CRice    16 年前

    像这样的东西怎么样?

        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();
        }
    }
    
    推荐文章