Creación de instancias de una lista de tipos parametrizados, haciendo un mejor uso de Generics y Linq

StackOverflow https://stackoverflow.com/questions/2405862

Pregunta

Estoy procesando un archivo con uno o más algoritmos hash.Cuando intenté parametrizar qué tipos de hash quiero, se volvió mucho más complicado de lo que esperaba.

Creo que estoy perdiendo la oportunidad de hacer un mejor uso de los genéricos o LINQ.Tampoco me gusta tener que usar un Tipo [] como parámetro en lugar de limitarlo a un conjunto de tipos más específico (descendientes de HashAlgorithm), me gustaría especificar tipos como parámetro y dejar que este método haga el en construcción, pero tal vez esto se vería mejor si tuviera que pasar las nuevas instancias de HashAlgorithm de la persona que llama.

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

Solución

Vamos a empezar por dividir el problema. Su requisito es que se necesita para calcular diferentes tipos de hashes en el mismo archivo. Supongamos por un momento que usted No necesario crear una instancia en realidad los tipos. Comience con una función que tiene ellos ya instancia:

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

Eso fue fácil. Si los archivos podrían ser grandes y hay que transmitirlo (teniendo en cuenta que esto será mucho más costoso en términos de I / O, simplemente más barato para la memoria), se puede hacer eso también, es sólo un poco más detallado:

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

Es un poco retorcido y en el segundo caso es muy cuestionable si es o no la versión de LINQ-cado es mejor que un bucle foreach ordinaria, pero bueno, nos estamos divirtiendo, ¿verdad?

Ahora que hemos desenredado el código hash generación, crear instancias de ellos en primer lugar no es realmente mucho más difícil. Una vez más vamos a empezar con el código que está limpio - código que utiliza delegados en lugar 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);
}

Ahora bien, esto es mucho más agradable, y la ventaja es que permite creación de instancias de los algoritmos dentro del método, pero no lo hace requerir ella. Podemos invocar este modo:

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

Si realmente, realmente, desesperadamente hay que partir de los casos Type reales, que me trate de no hacer porque rompe en tiempo de compilación comprobación de tipos, entonces no podemos hacer que a medida que la último paso:

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

Y eso es todo. Ahora podemos ejecutar esta (mala) código:

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

Al final del día, esto parece más código, pero es sólo porque hemos compusimos la solución efectiva de una manera que es fácil de probar y mantener. Si quisiéramos hacer todo esto en una sola expresión LINQ, se podría:

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

Eso es todo lo que hay en realidad es a ella. Me he saltado el paso de "selector" delegado en esta versión final, ya que si usted está escribiendo todo esto como una función que no es necesario el paso intermedio; la razón de tenerlo como una función separada anterior es dar la mayor flexibilidad posible al tiempo que se mantiene la seguridad en tiempo de compilación tipo. Aquí hemos tipo de tirado para obtener el beneficio de código más concisa.


Editar: Voy a añadir una cosa, y es que a pesar de este código es el más bonito, que en realidad se escapa de los recursos no administrados utilizados por los descendientes HashAlgorithm. Que realmente necesita para hacer algo como esto en su lugar:

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

Y otra vez estamos especie de perder claridad aquí. Puede ser que sea mejor simplemente construir los casos en primer lugar, a continuación, iterar a través de ellos con foreach y yield return las cadenas de hash. Pero usted pidió una solución LINQ, por lo que no eres. ;)

Otros consejos

¿Por qué estás suministrando los tipos como Types y crearlos en lugar de simplemente permitir que el usuario pase instancias de HashAlgorithm?Parece que eso aliviaría el problema por completo.

Si esto es un requisito, entonces lo que tiene es realmente la única solución, ya que no puede especificar un número variable de parámetros de tipo en un tipo o función genérico (que parece que necesitaría, ya que ahora es una matriz) , y no puede exigir que los tipos pasados ​​sean de una línea de herencia particular (como tampoco puede exigir que un parámetro entero esté entre 1 y 10).Ese tipo de validación sólo se puede realizar en tiempo de ejecución.

Sólo un punto menor aquí, nada de ruptura suelo. Siempre que forEach más de una lista se puede utilizar LINQ. Es especialmente agradable para los trazadores de líneas uno:

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

¿Qué pasa algo como esto?

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

    }

La cláusula where restringe el parámetro T para ser de tipo HashAlgorithm. Por lo que puede crear una clase que hereda de HashAlgorithm y poner en práctica los miembros de la clase abstracta:

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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top