Pergunta

Estou hash um arquivo com um ou mais algoritmos de hash. Quando eu tentei parametrizar quais os tipos de hash que eu quero, ele ficou muito mais confusa do que eu estava esperando.

Eu acho que estou perdendo a chance de fazer melhor uso dos genéricos ou LINQ. Eu também não gosto que eu tenho que usar um tipo [] como parâmetro em vez de limitá-la a um conjunto mais específico do tipo (descendentes hashAlgorithm), eu gostaria de especificar os tipos como o parâmetro e deixe este método fazer o construção, mas talvez isso ficaria melhor se eu tivesse o chamador novos-up instâncias de HashAlgorithm para passar em?

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());
            }
        }
Foi útil?

Solução

Vamos começar por quebrar o baixo problema. Sua exigência é que você precisa para calcular vários tipos de hashes no mesmo arquivo. Suponha por um momento que você não necessidade de realmente instanciar os tipos. Começar com uma função que tem deles já instanciado:

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

Isso foi fácil. Se os arquivos podem ser grandes e você precisa transmiti-lo (tendo em mente que isso vai ser muito mais caro em termos de I / O, apenas mais barato para a memória), você pode fazer isso também, é apenas um pouco mais detalhado:

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

É um pouco deformado e, no segundo caso, é altamente questionável ou não a versão Linq-ified é melhor do que um loop foreach comum, mas hey, estamos nos divertindo, né?

Agora que já desembaraçada o código hash geração, instanciar-los primeiro que não é realmente muito mais difícil. Mais uma vez, vamos começar com o código que é limpo - o código que usa delegados em vez de tipos:

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

Agora, isso é muito mais agradável, ea vantagem é que ele permite instanciação dos algoritmos dentro do método, mas não requerem -lo. Podemos chamá-lo assim:

var hashes = GetHashStrings(fileName,
    () => new MD5CryptoServiceProvider(),
    () => new SHA1CryptoServiceProvider());

Se nós realmente, realmente, desesperadamente necessidade de começar a partir das instâncias Type reais, que eu ia tentar não fazer, porque ele quebra a verificação de tipo de tempo de compilação, então podemos fazer isso como a último passo:

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

E é isso. Agora nós podemos executar este (mau) Código:

var hashes = GetHashStrings(fileName, typeof(MD5CryptoServiceProvider),
    typeof(SHA1CryptoServiceProvider));

No final do dia, este parece ser mais código, mas é só porque nós composta a solução efetivamente de uma maneira que é fácil de testar e manter. Se quiséssemos fazer isso tudo em uma única expressão Linq, poderíamos:

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

Isso é tudo o que realmente existe para ela. Eu pulei a etapa delegado "selector" nesta versão final, porque se você estiver escrevendo tudo isso como uma função que não é necessário o passo intermediário; a razão para tê-lo como uma função separada anteriormente é dar o máximo de flexibilidade possível, mantendo a segurança de tipos em tempo de compilação. Aqui temos espécie de jogado fora para obter o benefício de código terser.


Edit: vou acrescentar uma coisa, o que é que, embora isso parece código mais bonita, ele realmente vazamentos os recursos não gerenciados usados ??pelos descendentes HashAlgorithm. Você realmente precisa fazer algo parecido com isto em vez disso:

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

E novamente estamos tipo de perder a clareza aqui. Poderia ser melhor para apenas construir as instâncias em primeiro lugar, em seguida, iterar através deles com foreach e yield return as cordas de hash. Mas você pediu uma solução Linq, então aí está você. ;)

Outras dicas

Por que você está fornecendo os tipos como Types e criá-los ao invés de apenas permitindo que o usuário passar em casos de HashAlgorithm? Parece que iria aliviar o problema por completo.

Se esta for uma exigência, então o que você tem é realmente a única solução, uma vez que não é possível especificar um número variável de parâmetros de tipo em um tipo ou função genérica (que parece que você precisa, uma vez que é um matriz agora), e não é possível aplicar os tipos passados ??para ser de uma linha de herança especial (não mais do que é possível aplicar parâmetro que um número inteiro entre 1 e 10). Esse tipo de validação só pode ser feito no runtime.

Apenas um ponto menor aqui, quebra nada chão. Sempre que você foreach sobre uma lista que você pode usar o LINQ. É especialmente agradável para forros:

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

O que sobre algo assim?

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

    }

A cláusula WHERE restringe o parâmetro T para ser do tipo HashAlgorithm. Assim, você pode criar uma classe herdada de HashAlgorithm e implementar os membros classe abstrata:

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();
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top